Background
The server test job intermittently prints:
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
Investigation found this is not a leaked handle:
jest --detectOpenHandles is clean across many runs.
node-cache already .unref()s its check-period timer.
- The message only appears under CPU contention (e.g.
yarn turbo test running jest + vitest concurrently).
It's slow ts-jest worker teardown under load — each worker holds a full compiled TS program in memory. The warning is left in place intentionally as a reminder for this overhaul (rather than masked with --forceExit).
The real fix
Migrate the jest transform from ts-jest to @swc/jest (~7x faster, light workers that exit cleanly, no warning). A spike showed this works but SWC's stricter CommonJS live-binding exports turn the codebase's existing circular dependencies into TDZ errors:
ReferenceError: Cannot access 'X' before initialization
So the migration is gated on first breaking those cycles:
- ~24
forwardRef(() => X) constructor injections across ~12 service files (SettingsService, MediaServerFactory, MediaServerSwitchService, PlexApiService, etc.). These are genuine circular service dependencies.
- Bidirectional TypeORM entity relations (e.g.
Collection ↔ RuleGroup).
Suggested approach
- Reduce/break the circular service dependencies where possible (many
forwardRefs may be avoidable with better module boundaries).
- For unavoidable entity cycles, use TypeORM's
Relation<> wrapper + import type on relation properties.
- For remaining
forwardRef params, type-only-alias the annotation so decoratorMetadata doesn't emit an eager class reference.
- Switch the jest transform to
@swc/jest and drop the worker-teardown warning.
Context
The warning was surfaced while fixing an unrelated vitest OOM in #2940 (settings-form render loop, fixed in fix/settings-form-render-loop). This overhaul was deliberately scoped out of that fix.
Background
The server test job intermittently prints:
Investigation found this is not a leaked handle:
jest --detectOpenHandlesis clean across many runs.node-cachealready.unref()s its check-period timer.yarn turbo testrunning jest + vitest concurrently).It's slow
ts-jestworker teardown under load — each worker holds a full compiled TS program in memory. The warning is left in place intentionally as a reminder for this overhaul (rather than masked with--forceExit).The real fix
Migrate the jest transform from
ts-jestto@swc/jest(~7x faster, light workers that exit cleanly, no warning). A spike showed this works but SWC's stricter CommonJS live-binding exports turn the codebase's existing circular dependencies into TDZ errors:So the migration is gated on first breaking those cycles:
forwardRef(() => X)constructor injections across ~12 service files (SettingsService,MediaServerFactory,MediaServerSwitchService,PlexApiService, etc.). These are genuine circular service dependencies.Collection↔RuleGroup).Suggested approach
forwardRefs may be avoidable with better module boundaries).Relation<>wrapper +import typeon relation properties.forwardRefparams, type-only-alias the annotation sodecoratorMetadatadoesn't emit an eager class reference.@swc/jestand drop the worker-teardown warning.Context
The warning was surfaced while fixing an unrelated vitest OOM in #2940 (settings-form render loop, fixed in
fix/settings-form-render-loop). This overhaul was deliberately scoped out of that fix.