acquireContainer function
- required ContainerSpec spec,
- required DockerEngine engine,
- required StateDir stateDir,
- required String project,
- String host = '127.0.0.1',
- Now now = DateTime.now,
- Sleeper sleep = _delay,
Get a running, ready container for spec.
Shared specs look for one Docker already holds, under a lock so two suites cannot both decide to create it. The lock covers only the create and the start: waiting for readiness happens after it is released, because a wait strategy is safe to run any number of times and holding the lock through a 60 second wait would stop every other suite.
Ensuring the image is present happens before the lock is taken, on the
dedicated path too even though that path never locks at all. The lock's
own timeouts assume it is held only across a create and a start — a few
seconds — and a cold pull of a large image routinely takes longer than
that, which would make every other shared suite fail with LockTimeout
mid-pull, or start treating a live lock as stale. pullImage is
idempotent and Docker coalesces concurrent pulls of the same reference,
so nothing is lost by asking before knowing whether this call will end up
reusing a container instead of creating one.
When ContainerSpec.build is set, building the image replaces pulling it — never both, since pulling a tag that only a local build ever produces would just fail. The build runs in this same place, before the lock, for the same reasoning as the pull it replaces: rig rebuilds on every acquire (there is no "is this the latest build" check to run), and an unchanged context only costs Docker's layer cache a lookup — tens of milliseconds — so two suites racing to build the same tag here cost a little duplicated work, not a stalled lock. A cold build, like a cold pull, can take much longer than the lock's timeouts assume, which is the failure mode keeping it out of the lock actually prevents.
The network, when the spec names one, is ensured here for the same
reason: ensureNetwork is idempotent, so two suites racing to create
rig-app at once cost an extra POST /networks/create that answers
"already there" rather than a coordination failure. Doing it inside the
per-hash lock would buy nothing — the lock is keyed by the container
spec's hash, not the network's name, so two different specs that happen
to share a network name would not even take the same lock.
Implementation
Future<AcquiredContainer> acquireContainer({
required ContainerSpec spec,
required DockerEngine engine,
required StateDir stateDir,
required String project,
String host = '127.0.0.1',
Now now = DateTime.now,
Sleeper sleep = _delay,
}) async {
stateDir.ensure();
final hash = specHash(spec);
final labels = buildRigLabels(spec: spec, hash: hash, project: project);
final build = spec.build;
if (build != null) {
await engine.buildImage(build, spec.image);
} else {
await _ensureImage(spec.image, engine);
}
final network = spec.network;
if (network != null) {
await engine.ensureNetwork(network.dockerName, buildRigNetworkLabels());
}
final placed = spec.lifetime == Lifetime.dedicated
// A private container cannot collide with anyone, so there is nothing
// to coordinate and no reason to queue behind other suites.
? await _create(spec, labels, engine)
: await withExclusiveLock(
stateDir.lockPath(hash),
() => _findOrCreate(spec, labels, hash, engine),
now: now,
sleep: sleep,
);
final inspected = await engine.inspectContainer(placed.id);
final acquired = AcquiredContainer(
containerId: placed.id,
host: host,
hostPorts: inspected.hostPorts,
lifetime: spec.lifetime,
reused: placed.reused,
hash: hash,
);
try {
await awaitReady(
strategy: spec.waitFor,
engine: engine,
target: ReadyTarget(
containerId: acquired.containerId,
host: host,
hostPortOf: (containerPort) => acquired.hostPorts[containerPort],
),
now: now,
sleep: sleep,
);
} on Object catch (error) {
// Leave the container alone: it is the only evidence of why it failed.
// Record it instead, since Docker cannot add a label after creation.
_markFailed(stateDir, acquired.containerId, spec, hash, error, now());
rethrow;
}
return acquired;
}