Skip to content

feat: add dependencyLevels scheduler for parallel up (#128, step 1) - #144

Open
OrtegaMatias wants to merge 1 commit into
Mcrich23:mainfrom
OrtegaMatias:feat/parallel-up-scheduler
Open

feat: add dependencyLevels scheduler for parallel up (#128, step 1)#144
OrtegaMatias wants to merge 1 commit into
Mcrich23:mainfrom
OrtegaMatias:feat/parallel-up-scheduler

Conversation

@OrtegaMatias

Copy link
Copy Markdown
Contributor

Summary

First step toward parallel up (#128): a pure, daemon-free scheduler that groups services into launch waves by their depends_on graph. Every service in a wave has all of its in-set dependencies in an earlier wave, so a whole wave can be container run launched concurrently.

Today up launches services strictly sequentially (for target in project.services { try await configService(...) }), so N independent services pay N× the per-service start+readiness cost. The issue reports ~23s for 11 independent services vs ~7s launched in parallel.

This PR lands only the schedulable core — a pure function with tests — with no behavior change yet (up still launches serially). The concurrent launch that consumes it will follow in a separate PR, so the correctness-critical ordering logic can be reviewed and regression-tested on its own (and it lands in the only tier CI runs).

API

static func dependencyLevels(
    _ services: [(serviceName: String, service: Service)]
) throws -> [[String]]

Mirrors the existing pure graph helpers (topoSortConfiguredServices, selectServices, validateRequestedServices):

  • Levels: level 0 = services with no in-set dependency; level(s) = 1 + max(level(dep)). Level 0 first; intra-wave order is unspecified (they start concurrently).
  • Out-of-set deps ignored: a depends_on naming a service not in the set (e.g. profile-excluded) is treated as already satisfied — same tolerance as topoSortConfiguredServices.
  • Cycles throw before returning: a dependency cycle (including a self-dependency) throws the same NSError/"ComposeError" shape as topoSortConfiguredServices, so a cyclic graph can never deadlock a future concurrent launch.

Tests

New @Suite("Parallel Scheduler Levels"), static (no daemon), mirroring ServiceDependencyTests:

  • linear chain web → app → db[["db"], ["app"], ["web"]] (chain is not falsely parallelized)
  • diamond (front → {web, api} → db) ⇒ siblings share a wave
  • independent set ⇒ a single wave (the Containers launch sequentially - can they be launched concurrently? #128 case)
  • dependency cycle ⇒ throws
  • self-dependency ⇒ throws
  • depends_on on an out-of-set service ⇒ dependent lands in wave 0, no throw
swift test --filter Container_Compose_StaticTests
✔ Test run with 242 tests in 22 suites passed

Groups services into launch WAVES by their depends_on graph: every service
in a wave has all of its in-set dependencies in an earlier wave, so a whole
wave can be launched concurrently. This is the pure, daemon-free core of
parallelizing `up` (Mcrich23#128), where independent services currently start
sequentially (~23s for 11 independent services vs ~7s in parallel).

- depends_on entries naming a service outside the selected set (e.g.
  profile-excluded) are ignored, matching topoSortConfiguredServices.
- Throws on a dependency cycle (including a self-dependency) before
  returning, so a cyclic graph can never deadlock a concurrent launch.

Pure static function mirroring the existing graph helpers, with 6 static
tests: linear chain, diamond, independent set, cycle, self-dependency, and
out-of-set dependency. No behavior change yet — `up` still launches serially;
this lands the schedulable core first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant