Skip to content

Two Simlock instances on one machine keep their Android emulators apart #1189

Closed
1 of 1 issue completed

Description

@V3RON

Request: #264

Problem

In the reporter's words (#264): "The Android driver's startup sweep announces every console port in 5586–5682 to this instance's adb server, as ADR 0001's correction prescribes. It assumes the range belongs to one instance. With two Simlock instances on one machine, which docs/CLI.md and docs/CONFIGURATION.md document, each instance attaches the other's emulators to its own adb server."

One worker per user is already the rule (ADR 0034), but nothing enforces it once SIMLOCK_HOME points a second worker at another folder. The website still suggests it for "a separate install". Two workers with real drivers then share the emulator ports, the emulators and the iOS runtimes. An agent leased from the second worker sees, and can drive, emulators leased to agents of the first:

$ adb devices                      # agent-2, leased from the second worker (adb server 5039)
emulator-5586  device              # the first worker's emulator, leased to agent-1
emulator-5590  device              # agent-2's own

The slow e2e lane hits the same thing: it starts its own real-driver worker under a temporary SIMLOCK_HOME, next to the maintainer's own worker.

Who it is for

A user on one Mac who starts a second Simlock worker with another SIMLOCK_HOME, by mistake or by following the old docs. And the maintainer, who runs the slow e2e lane on a Mac where their own worker runs.

Outcome

A second worker with real drivers for the same user refuses to start, and says which worker holds the machine and how to stop it. It never touches the first worker's emulators. Test workers with fake drivers still run side by side, as many as the suite wants.

How it works for the user

flowchart TD
  A["simlock daemon start (SIMLOCK_HOME=~/.simlock-b)"] --> B{"another worker of this user with real drivers alive?"}
  B -- no --> C["starts as today"]
  B -- "yes, same folder" --> D["'already running', as today"]
  B -- "yes, other folder" --> E["refuses, naming pid and folder; exit 1"]
  F["scripts/slow-e2e.sh"] --> G{"a worker holds the lock?"}
  G -- yes --> H["exits 69 (not fit), naming it and how to stop it"]
  G -- no --> I["runs as today"]
Loading

Before:

$ SIMLOCK_HOME=~/.simlock-b simlock daemon start
Daemon running (pid 5150)

After:

$ SIMLOCK_HOME=~/.simlock-b simlock daemon start
The daemon could not start: another Simlock worker runs for this user (pid 4242, folder /Users/me/.simlock, since 2026-11-02T09:12:03.000Z); stop it first: SIMLOCK_HOME=/Users/me/.simlock simlock daemon stop
$ echo $?
1

Examples

Situation Today After
No other worker starts starts
Same folder, worker alive "already running" "already running"
Other folder, worker alive, real drivers starts, attaches the other's emulators refuses, exit 1
Other folder, its worker crashed (pid gone) starts starts (takes the stale lock over)
Other folder, fake drivers (SIMLOCK_DRIVERS_MODULE) starts starts
A gateway next to the worker starts starts
Slow lane while your own worker runs runs, may fail on your emulators exits 69, names your worker and how to stop it

What could go wrong

  • A worker crashes and its pid is reused by another program: the next start refuses until that program ends. Unlikely; a reboot clears it.
  • A worker that cannot create its lock folder in /tmp refuses to start; the message names the folder.
  • Two OS users on one Mac can still each run a worker (known gap, unchanged).

Words used

  • worker: the Simlock daemon that owns devices, e.g. the one simlock daemon start runs in ~/.simlock.
  • real drivers: the iOS and Android drivers a worker finds on the machine; the opposite is a fake driver set from SIMLOCK_DRIVERS_MODULE, used by tests.
  • worker lock: the folder holding owner.json (pid, folder, start time): /tmp/simlock-<uid>/worker.lock/ on macOS and Linux, e.g. /tmp/simlock-501/worker.lock/owner.json; <os.tmpdir()>\simlock\worker.lock\ on Windows.
  • stale lock: a worker lock whose pid is no longer running.
  • folder: a worker's data directory, ~/.simlock or $SIMLOCK_HOME.

Non-goals

  • Two OS users on one Mac (ADR 0034 keeps it a known gap).
  • Attaching only this worker's own emulators in the sweep: with one worker per user it is not needed.
  • Removing SIMLOCK_HOME from every user page (ADR 0034's own work); only the "separate install" passage goes here.
  • Checking a reused pid's command line.

Completion conditions

  • A second real-driver worker of the same user with another folder refuses to start with the message above, exit 1, and starts no driver.
  • The first worker's stop frees the lock; a crashed worker's lock is taken over at the next start.
  • pnpm test:e2e still runs its files in parallel and passes.
  • scripts/slow-e2e.sh exits 69 naming the holder while a worker outside the lane holds the lock.
  • worker/running-the-daemon.mdx no longer offers a separate install with SIMLOCK_HOME.

Open questions

Decisions

Shared values

none

Behaviours

Behaviour (invoker, action, what it sees) Seam Cases Task
A worker with real drivers takes the worker lock before driver discovery, writing its pid, folder and start time startDaemon (src/daemon/main.ts) with the lock path pointed at a temp folder; the adapter no drivers passed and no SIMLOCK_DRIVERS_MODULE → taken before any command; drivers passed or module set → not read; no lock; stale lock (pid gone); unreadable owner.json; parent not ours or not 0700 #1191
A second worker whose lock is held by a live pid with another folder fails to start with the refusal message, before any driver call startDaemon other folder alive → refused; same folder alive → goes on to the socket claim #1191
A gateway never takes the lock, even when it is held startDaemon gateway role with a held lock #1191
The daemon and slow-e2e.sh agree on the default worker lock path defaultWorkerLockPath; the script's text darwin, linux (uid 501), win32; the script's default for the current uid #1191
A stopping worker removes the lock, after its drivers are disposed, only when owner.json is still the one it wrote; a start that fails after taking it removes it too startDaemon then stop() still its own; taken over by another meanwhile; start fails after the lock #1191
scripts/slow-e2e.sh check and a run exit 69 while a live pid holds the worker lock, naming it scripts/slow-e2e.sh with its test overrides live holder; stale holder → runs #1191

Tasks

Every Completion condition is delivered by #1191.

Open work in the same area: bugs #1168 and #1169 change Simlock's adb server, not driver discovery or the daemon start; no Depends on. ADR 0034's issue #700 is closed.

Written by an agent.

Activity

  1. added
    feature:specSpec in progress, business and technical together.
    on Nov 2, 2026
  2. V3RON commented on Nov 2, 2026

    @V3RON
    ContributorAuthor

    Spec updated: Problem, Who it is for, Outcome, How it works, Examples, Behaviours, Decisions (ADR 0059, PR #1190) and Tasks (#1191) added.

    Written by an agent.

  3. added a commit that references this issue on Nov 2, 2026
  4. added
    feature:plannedSplit into tasks. Never picked up itself.
    and removed
    feature:specSpec in progress, business and technical together.
    on Nov 2, 2026
  5. github-actions commented on Nov 2, 2026

    @github-actions
    Contributor

    Closing: all 1 sub-issues are closed. The PR that closed the last one walks the completion conditions (delivery rule 9); reopen this if one does not hold on main. Flip its ADRs to Accepted if delivery has not.

  6. V3RON commented on Nov 2, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: #1190 (ADR 0059), #1193 (#1191)
    Parked: none
    Waiting: none
    Decided: #1191 — two same-folder starts racing for the worker lock → the socket claim's winner makes sure it holds the lock (re-makes it, or rewrites owner.json, or fails on a live holder in another folder); a failed start removes only a lock it wrote. Pending the maintainer; see the ## Decision taken comment on #1190.

    Written by an agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature:plannedSplit into tasks. Never picked up itself.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions