What happens
Open Settings → check the items in your Apple account while a first import is still resolving
tags, and the screen sits on a spinner for minutes. Until recently it could not be left at all,
and it claimed to be "Looking for a device that can unlock it…" — for an account that had just
been linked and would never need a device.
The lock-in and the wording are fixed. The waiting is not, and this issue is about that.
Why
Every call into Python is serialised by PythonLock, and the reason given in its docstring is
that the iCloud flow and the location fetch share one Apple session, because two sessions would
be two devices to Apple (rule 11).
Sharing the session is not what forces the serialisation. Apple has no objection to two
concurrent requests from one device. What forces it is how the shared event loop is driven:
icloud_bridge.openSession reaches into account._evt_loop and reuses FindMy.py's own loop —
correctly, so there is one session and one identity
- but both sides then call
loop.run_until_complete(...), which means take over this loop and
block my thread until done
run_until_complete cannot be called on a loop that is already running — that is the
RuntimeError: This event loop is already running the lock exists to prevent
So the serialisation is a property of using the synchronous facade over an async library, not
of the identity. Two network calls awaiting sockets on one loop is precisely what asyncio is for,
and it is being prevented by the calling convention rather than by anything Apple cares about.
It hurts most exactly where the app is slowest: on a first import, tags with no key alignment
record are searched across the full seven-day window, one at a time, and that can hold the lock
for minutes.
Shape of a fix
- run the loop continuously on its own thread (
run_forever) instead of entering it per call
- submit work with
asyncio.run_coroutine_threadsafe(coro, loop), which returns a future each
caller can wait on independently
- same account object, same identity, same session, one device — rule 11 untouched
Caveats, before anyone starts
- FindMy.py's async account has not been checked for concurrent use. Shared mutable state —
the anisette provider, session-token refresh, ADI state — could race. Token refresh is the
obvious hazard: two coroutines both finding an expired token and both re-provisioning. That
likely needs a narrow lock around authentication specifically, which is a far smaller thing
to hold than a lock around every call.
- It changes the concurrency model for every caller of the bridge at once, so it wants its own
branch and its own tests rather than riding along with something else.
- The UI work already done — an honest caption while waiting, and being able to leave the screen
— is still needed either way. There will always be a window where a call genuinely is in
flight.
Not urgent
Nothing is broken by this; the app is slower and less responsive than it needs to be during one
specific window. Worth doing after 1.1.0 rather than in it.
🤖 Written by Claude Code
What happens
Open Settings → check the items in your Apple account while a first import is still resolving
tags, and the screen sits on a spinner for minutes. Until recently it could not be left at all,
and it claimed to be "Looking for a device that can unlock it…" — for an account that had just
been linked and would never need a device.
The lock-in and the wording are fixed. The waiting is not, and this issue is about that.
Why
Every call into Python is serialised by
PythonLock, and the reason given in its docstring isthat the iCloud flow and the location fetch share one Apple session, because two sessions would
be two devices to Apple (rule 11).
Sharing the session is not what forces the serialisation. Apple has no objection to two
concurrent requests from one device. What forces it is how the shared event loop is driven:
icloud_bridge.openSessionreaches intoaccount._evt_loopand reuses FindMy.py's own loop —correctly, so there is one session and one identity
loop.run_until_complete(...), which means take over this loop andblock my thread until done
run_until_completecannot be called on a loop that is already running — that is theRuntimeError: This event loop is already runningthe lock exists to preventSo the serialisation is a property of using the synchronous facade over an async library, not
of the identity. Two network calls awaiting sockets on one loop is precisely what asyncio is for,
and it is being prevented by the calling convention rather than by anything Apple cares about.
It hurts most exactly where the app is slowest: on a first import, tags with no key alignment
record are searched across the full seven-day window, one at a time, and that can hold the lock
for minutes.
Shape of a fix
run_forever) instead of entering it per callasyncio.run_coroutine_threadsafe(coro, loop), which returns a future eachcaller can wait on independently
Caveats, before anyone starts
the anisette provider, session-token refresh, ADI state — could race. Token refresh is the
obvious hazard: two coroutines both finding an expired token and both re-provisioning. That
likely needs a narrow lock around authentication specifically, which is a far smaller thing
to hold than a lock around every call.
branch and its own tests rather than riding along with something else.
— is still needed either way. There will always be a window where a call genuinely is in
flight.
Not urgent
Nothing is broken by this; the app is slower and less responsive than it needs to be during one
specific window. Worth doing after 1.1.0 rather than in it.
🤖 Written by Claude Code