@open-mercato/cli 0.6.6-develop.6389.1.135a466c47 → 0.6.6-develop.6390.1.210bbbc703
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentic/guides/module-facts.json +9 -9
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/lib/auto-spawn-workers.js +8 -1
- package/dist/lib/auto-spawn-workers.js.map +2 -2
- package/dist/lib/queue-worker-supervisor.js +95 -0
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/dist/mercato.js +11 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +13 -0
- package/src/lib/__tests__/auto-spawn-workers.test.ts +19 -0
- package/src/lib/__tests__/queue-worker-supervisor.test.ts +42 -0
- package/src/lib/auto-spawn-workers.ts +8 -0
- package/src/lib/queue-worker-supervisor.ts +105 -0
- package/src/mercato.ts +13 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6390.1.210bbbc703",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@mikro-orm/decorators": "^7.1.4",
|
|
61
61
|
"@mikro-orm/migrations": "^7.1.4",
|
|
62
62
|
"@mikro-orm/postgresql": "^7.1.4",
|
|
63
|
-
"@open-mercato/queue": "0.6.6-develop.
|
|
64
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
63
|
+
"@open-mercato/queue": "0.6.6-develop.6390.1.210bbbc703",
|
|
64
|
+
"@open-mercato/shared": "0.6.6-develop.6390.1.210bbbc703",
|
|
65
65
|
"cross-spawn": "^7.0.6",
|
|
66
66
|
"pg": "8.22.0",
|
|
67
67
|
"semver": "^7.8.5",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"typescript": "^6.0.3"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
74
|
+
"@open-mercato/shared": "0.6.6-develop.6390.1.210bbbc703"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
77
|
+
"@open-mercato/shared": "0.6.6-develop.6390.1.210bbbc703",
|
|
78
78
|
"@types/jest": "^30.0.0",
|
|
79
79
|
"jest": "^30.4.2",
|
|
80
80
|
"ts-jest": "^29.4.11"
|
|
@@ -645,6 +645,7 @@ describe('server dev managed process exits', () => {
|
|
|
645
645
|
const originalAutoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER
|
|
646
646
|
const originalAutoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS
|
|
647
647
|
const originalLazy = process.env.OM_AUTO_SPAWN_WORKERS_LAZY
|
|
648
|
+
const originalLazyMode = process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE
|
|
648
649
|
const originalLazyScheduler = process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY
|
|
649
650
|
const originalGenerateWatchMode = process.env.OM_DEV_GENERATE_WATCH_MODE
|
|
650
651
|
const originalSingleDelivery = process.env.OM_EVENTS_SINGLE_DELIVERY
|
|
@@ -656,6 +657,7 @@ describe('server dev managed process exits', () => {
|
|
|
656
657
|
process.env.AUTO_SPAWN_SCHEDULER = 'false'
|
|
657
658
|
process.env.AUTO_SPAWN_WORKERS = 'true'
|
|
658
659
|
delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY
|
|
660
|
+
delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE
|
|
659
661
|
delete process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY
|
|
660
662
|
// These tests toggle AUTO_SPAWN_WORKERS to exercise scheduler/Next exits, not
|
|
661
663
|
// the events single-delivery guard. Acknowledge an external events worker so
|
|
@@ -683,6 +685,7 @@ describe('server dev managed process exits', () => {
|
|
|
683
685
|
if (originalGenerateWatchMode === undefined) delete process.env.OM_DEV_GENERATE_WATCH_MODE
|
|
684
686
|
else process.env.OM_DEV_GENERATE_WATCH_MODE = originalGenerateWatchMode
|
|
685
687
|
delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY
|
|
688
|
+
delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE
|
|
686
689
|
delete process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY
|
|
687
690
|
})
|
|
688
691
|
|
|
@@ -691,6 +694,8 @@ describe('server dev managed process exits', () => {
|
|
|
691
694
|
process.env.AUTO_SPAWN_WORKERS = originalAutoSpawnWorkers
|
|
692
695
|
if (originalLazy === undefined) delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY
|
|
693
696
|
else process.env.OM_AUTO_SPAWN_WORKERS_LAZY = originalLazy
|
|
697
|
+
if (originalLazyMode === undefined) delete process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE
|
|
698
|
+
else process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE = originalLazyMode
|
|
694
699
|
if (originalLazyScheduler === undefined) delete process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY
|
|
695
700
|
else process.env.OM_AUTO_SPAWN_SCHEDULER_LAZY = originalLazyScheduler
|
|
696
701
|
if (originalSingleDelivery === undefined) delete process.env.OM_EVENTS_SINGLE_DELIVERY
|
|
@@ -793,6 +798,7 @@ describe('server dev managed process exits', () => {
|
|
|
793
798
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
|
794
799
|
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
|
|
795
800
|
process.env.OM_AUTO_SPAWN_WORKERS_LAZY = 'true'
|
|
801
|
+
process.env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE = 'shared'
|
|
796
802
|
|
|
797
803
|
jest.doMock('node:fs', () => {
|
|
798
804
|
const actual = jest.requireActual('node:fs')
|
|
@@ -843,8 +849,15 @@ describe('server dev managed process exits', () => {
|
|
|
843
849
|
expect(supervisorCall.workers.map((worker) => worker.queue)).toEqual(['events'])
|
|
844
850
|
expect(supervisorCall.pollMs).toBe(1000)
|
|
845
851
|
expect(supervisorCall.restartOnUnexpectedExit).toBe(true)
|
|
852
|
+
expect(supervisorCall.spawnMode).toBe('shared')
|
|
846
853
|
expect(supervisorClose).toHaveBeenCalled()
|
|
847
854
|
|
|
855
|
+
const lazyLogLine = consoleLogSpy.mock.calls
|
|
856
|
+
.map((call) => call[0])
|
|
857
|
+
.find((line) => typeof line === 'string' && line.startsWith('[server] Lazy worker auto-spawn enabled'))
|
|
858
|
+
expect(lazyLogLine).toContain('shared worker mode')
|
|
859
|
+
expect(lazyLogLine).toContain('OM_AUTO_SPAWN_WORKERS_LAZY_MODE=per-queue')
|
|
860
|
+
|
|
848
861
|
const { spawn } = await import('child_process')
|
|
849
862
|
const allSpawnCalls = (spawn as jest.Mock).mock.calls.map((call) => call[1] as string[])
|
|
850
863
|
const queueWorkerSpawn = allSpawnCalls.find((args) => args.slice(1).join(' ') === 'queue worker --all')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveAutoSpawnWorkersMode,
|
|
3
|
+
resolveLazySpawnMode,
|
|
3
4
|
resolveLazyPollMs,
|
|
4
5
|
resolveLazyRestart,
|
|
5
6
|
} from '../auto-spawn-workers'
|
|
@@ -82,6 +83,24 @@ describe('resolveLazyPollMs', () => {
|
|
|
82
83
|
})
|
|
83
84
|
})
|
|
84
85
|
|
|
86
|
+
describe('resolveLazySpawnMode', () => {
|
|
87
|
+
it('defaults to per-queue workers when unset', () => {
|
|
88
|
+
expect(resolveLazySpawnMode({})).toBe('per-queue')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('uses a shared worker when explicitly requested', () => {
|
|
92
|
+
expect(resolveLazySpawnMode({ OM_AUTO_SPAWN_WORKERS_LAZY_MODE: 'shared' })).toBe('shared')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('accepts the explicit per-queue mode', () => {
|
|
96
|
+
expect(resolveLazySpawnMode({ OM_AUTO_SPAWN_WORKERS_LAZY_MODE: 'per-queue' })).toBe('per-queue')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('falls back to per-queue for invalid values', () => {
|
|
100
|
+
expect(resolveLazySpawnMode({ OM_AUTO_SPAWN_WORKERS_LAZY_MODE: 'maybe' })).toBe('per-queue')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
85
104
|
describe('resolveLazyRestart', () => {
|
|
86
105
|
it('defaults to true when unset', () => {
|
|
87
106
|
expect(resolveLazyRestart({})).toBe(true)
|
|
@@ -128,6 +128,48 @@ describe('startLazyWorkerSupervisor', () => {
|
|
|
128
128
|
await handle.close()
|
|
129
129
|
})
|
|
130
130
|
|
|
131
|
+
it('can start one shared worker for all queues when any queue has ready jobs', async () => {
|
|
132
|
+
const child = createFakeChild()
|
|
133
|
+
const spawnFn = jest.fn(() => child) as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
|
|
134
|
+
const probeFn = jest.fn(async (queueName, strategy) => {
|
|
135
|
+
if (queueName === 'events' || queueName === 'emails') return readyProbe(queueName, strategy)
|
|
136
|
+
return emptyProbe(queueName, strategy)
|
|
137
|
+
}) as jest.MockedFunction<LazySupervisorProbeFn>
|
|
138
|
+
|
|
139
|
+
const handle = startLazyWorkerSupervisor({
|
|
140
|
+
mercatoBin: '/tmp/mercato',
|
|
141
|
+
appDir: '/tmp/app',
|
|
142
|
+
runtimeEnv: { ...process.env },
|
|
143
|
+
workers: [makeWorker('events'), makeWorker('emails'), makeWorker('search')],
|
|
144
|
+
pollMs: 250,
|
|
145
|
+
restartOnUnexpectedExit: false,
|
|
146
|
+
strategy: 'local',
|
|
147
|
+
spawnMode: 'shared',
|
|
148
|
+
spawnFn,
|
|
149
|
+
probeFn,
|
|
150
|
+
logger: silentLogger,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
await flushAsync(20)
|
|
154
|
+
|
|
155
|
+
expect(spawnFn).toHaveBeenCalledTimes(1)
|
|
156
|
+
const [command, args] = spawnFn.mock.calls[0]
|
|
157
|
+
expect(command).toBe('node')
|
|
158
|
+
expect(args).toEqual(['/tmp/mercato', 'queue', 'worker', '--all'])
|
|
159
|
+
expect(handle.startedQueues.has('events')).toBe(true)
|
|
160
|
+
expect(handle.startedQueues.has('emails')).toBe(true)
|
|
161
|
+
expect(handle.startedQueues.has('search')).toBe(true)
|
|
162
|
+
expect(handle.getActiveChild('events')).toBe(child)
|
|
163
|
+
expect(handle.getActiveChild('emails')).toBe(child)
|
|
164
|
+
expect(handle.getActiveChild('search')).toBe(child)
|
|
165
|
+
|
|
166
|
+
await flushAsync(20)
|
|
167
|
+
expect(spawnFn).toHaveBeenCalledTimes(1)
|
|
168
|
+
|
|
169
|
+
await handle.close()
|
|
170
|
+
expect((child as any).kill).toHaveBeenCalledWith('SIGTERM')
|
|
171
|
+
})
|
|
172
|
+
|
|
131
173
|
it('does not spawn when probe reports an error', async () => {
|
|
132
174
|
const spawnFn = jest.fn() as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
|
|
133
175
|
const probeFn = jest.fn(async (queueName, strategy) => ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
2
2
|
|
|
3
3
|
export type AutoSpawnWorkersMode = 'off' | 'eager' | 'lazy'
|
|
4
|
+
export type LazyWorkerSpawnMode = 'per-queue' | 'shared'
|
|
4
5
|
|
|
5
6
|
export type AutoSpawnEnvSource = Pick<NodeJS.ProcessEnv, string> | Record<string, string | undefined>
|
|
6
7
|
|
|
@@ -33,6 +34,13 @@ export function resolveLazyPollMs(env: AutoSpawnEnvSource = process.env): number
|
|
|
33
34
|
return Math.max(MIN_LAZY_POLL_MS, Math.floor(parsed))
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export function resolveLazySpawnMode(env: AutoSpawnEnvSource = process.env): LazyWorkerSpawnMode {
|
|
38
|
+
const raw = env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE?.trim().toLowerCase()
|
|
39
|
+
if (raw === 'shared') return 'shared'
|
|
40
|
+
if (raw === 'per-queue') return 'per-queue'
|
|
41
|
+
return 'per-queue'
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export function resolveLazyRestart(env: AutoSpawnEnvSource = process.env): boolean {
|
|
37
45
|
const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY_RESTART)
|
|
38
46
|
return parsed === null ? true : parsed
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type QueueStrategyType,
|
|
31
31
|
} from '@open-mercato/queue'
|
|
32
32
|
import type { ModuleWorker } from '@open-mercato/shared/modules/registry'
|
|
33
|
+
import type { LazyWorkerSpawnMode } from './auto-spawn-workers'
|
|
33
34
|
|
|
34
35
|
export type LazySupervisorSpawnFn = (
|
|
35
36
|
command: string,
|
|
@@ -49,6 +50,7 @@ export type LazyWorkerSupervisorOptions = {
|
|
|
49
50
|
workers: ModuleWorker[]
|
|
50
51
|
pollMs: number
|
|
51
52
|
restartOnUnexpectedExit: boolean
|
|
53
|
+
spawnMode?: LazyWorkerSpawnMode
|
|
52
54
|
strategy?: QueueStrategyType
|
|
53
55
|
/** Override for tests. Defaults to `child_process.spawn`. */
|
|
54
56
|
spawnFn?: LazySupervisorSpawnFn
|
|
@@ -112,6 +114,7 @@ export function startLazyWorkerSupervisor(
|
|
|
112
114
|
): LazyWorkerSupervisorHandle {
|
|
113
115
|
const logger = options.logger ?? console
|
|
114
116
|
const strategy: QueueStrategyType = options.strategy ?? resolveStrategyFromEnv(options.runtimeEnv)
|
|
117
|
+
const spawnMode: LazyWorkerSpawnMode = options.spawnMode ?? 'per-queue'
|
|
115
118
|
const probe: LazySupervisorProbeFn = options.probeFn
|
|
116
119
|
?? ((queueName) => getQueuePendingProbe(queueName, strategy))
|
|
117
120
|
const groups = groupWorkersByQueue(options.workers)
|
|
@@ -119,6 +122,8 @@ export function startLazyWorkerSupervisor(
|
|
|
119
122
|
const startedQueues = new Set<string>()
|
|
120
123
|
const activeChildren = new Map<string, ChildProcess>()
|
|
121
124
|
const startingQueues = new Set<string>()
|
|
125
|
+
let activeSharedChild: ChildProcess | undefined
|
|
126
|
+
let startingSharedWorker = false
|
|
122
127
|
const probeErrorLastLoggedAt = new Map<string, number>()
|
|
123
128
|
|
|
124
129
|
let stopping = false
|
|
@@ -143,6 +148,22 @@ export function startLazyWorkerSupervisor(
|
|
|
143
148
|
const spawnFn: LazySupervisorSpawnFn =
|
|
144
149
|
options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args as string[], opts))
|
|
145
150
|
|
|
151
|
+
const markSharedWorkerStarted = () => {
|
|
152
|
+
for (const group of groups) {
|
|
153
|
+
startedQueues.add(group.queueName)
|
|
154
|
+
activeChildren.set(group.queueName, activeSharedChild as ChildProcess)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const clearSharedWorker = () => {
|
|
159
|
+
for (const group of groups) {
|
|
160
|
+
if (activeChildren.get(group.queueName) === activeSharedChild) {
|
|
161
|
+
activeChildren.delete(group.queueName)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
activeSharedChild = undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
146
167
|
function logProbeError(queueName: string, message: string): void {
|
|
147
168
|
const now = Date.now()
|
|
148
169
|
const lastAt = probeErrorLastLoggedAt.get(queueName) ?? 0
|
|
@@ -215,6 +236,81 @@ export function startLazyWorkerSupervisor(
|
|
|
215
236
|
})
|
|
216
237
|
}
|
|
217
238
|
|
|
239
|
+
function spawnSharedWorker(triggerQueueNames: string[]): void {
|
|
240
|
+
if (stopping) return
|
|
241
|
+
if (activeSharedChild) return
|
|
242
|
+
if (startingSharedWorker) return
|
|
243
|
+
startingSharedWorker = true
|
|
244
|
+
|
|
245
|
+
const triggerSummary = triggerQueueNames.length > 0
|
|
246
|
+
? ` after pending job(s) on ${triggerQueueNames.join(', ')}`
|
|
247
|
+
: ''
|
|
248
|
+
logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} — starting shared worker for all queues`)
|
|
249
|
+
let child: ChildProcess
|
|
250
|
+
try {
|
|
251
|
+
child = spawnFn(
|
|
252
|
+
'node',
|
|
253
|
+
[options.mercatoBin, 'queue', 'worker', '--all'],
|
|
254
|
+
{
|
|
255
|
+
stdio: 'inherit',
|
|
256
|
+
env: options.runtimeEnv,
|
|
257
|
+
cwd: options.appDir,
|
|
258
|
+
},
|
|
259
|
+
)
|
|
260
|
+
} catch (err) {
|
|
261
|
+
startingSharedWorker = false
|
|
262
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
263
|
+
logger.error(`[lazy-supervisor] Failed to spawn shared worker: ${message}`)
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
activeSharedChild = child
|
|
268
|
+
markSharedWorkerStarted()
|
|
269
|
+
startingSharedWorker = false
|
|
270
|
+
for (const group of groups) {
|
|
271
|
+
if (options.onSpawn) {
|
|
272
|
+
try {
|
|
273
|
+
options.onSpawn(group.queueName, child)
|
|
274
|
+
} catch (err) {
|
|
275
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
276
|
+
logger.warn(`[lazy-supervisor] onSpawn callback threw for "${group.queueName}": ${message}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
child.on('exit', (code, signal) => {
|
|
282
|
+
clearSharedWorker()
|
|
283
|
+
for (const group of groups) {
|
|
284
|
+
if (options.onChildExit) {
|
|
285
|
+
try {
|
|
286
|
+
options.onChildExit(group.queueName, code, signal)
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
289
|
+
logger.warn(`[lazy-supervisor] onChildExit callback threw for "${group.queueName}": ${message}`)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (stopping) return
|
|
294
|
+
const expected = signal === 'SIGTERM' || signal === 'SIGINT'
|
|
295
|
+
if (expected) return
|
|
296
|
+
const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`
|
|
297
|
+
logger.warn(`[lazy-supervisor] Shared worker exited (${reason}).`)
|
|
298
|
+
if (!options.restartOnUnexpectedExit) return
|
|
299
|
+
void (async () => {
|
|
300
|
+
const readyQueues: string[] = []
|
|
301
|
+
for (const group of groups) {
|
|
302
|
+
const result = await safeProbe(group.queueName)
|
|
303
|
+
if (!result || result.error) continue
|
|
304
|
+
if (result.ready > 0) readyQueues.push(group.queueName)
|
|
305
|
+
}
|
|
306
|
+
if (readyQueues.length > 0) {
|
|
307
|
+
logger.warn('[lazy-supervisor] Restarting shared worker because jobs remain pending.')
|
|
308
|
+
spawnSharedWorker(readyQueues)
|
|
309
|
+
}
|
|
310
|
+
})()
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
218
314
|
async function safeProbe(queueName: string): Promise<QueuePendingProbeResult | null> {
|
|
219
315
|
try {
|
|
220
316
|
return await probe(queueName, strategy)
|
|
@@ -228,6 +324,7 @@ export function startLazyWorkerSupervisor(
|
|
|
228
324
|
async function tickOnce(): Promise<void> {
|
|
229
325
|
if (stopping) return
|
|
230
326
|
if (groups.length === 0) return
|
|
327
|
+
if (spawnMode === 'shared' && (activeSharedChild || startingSharedWorker)) return
|
|
231
328
|
|
|
232
329
|
const toCheck = groups.filter((g) => !activeChildren.has(g.queueName) && !startingQueues.has(g.queueName))
|
|
233
330
|
if (toCheck.length === 0) return
|
|
@@ -241,6 +338,7 @@ export function startLazyWorkerSupervisor(
|
|
|
241
338
|
|
|
242
339
|
if (stopping) return
|
|
243
340
|
|
|
341
|
+
const readySharedQueues: string[] = []
|
|
244
342
|
for (const { group, result } of probes) {
|
|
245
343
|
if (!result) continue
|
|
246
344
|
if (result.error) {
|
|
@@ -250,9 +348,16 @@ export function startLazyWorkerSupervisor(
|
|
|
250
348
|
continue
|
|
251
349
|
}
|
|
252
350
|
if (result.ready > 0) {
|
|
351
|
+
if (spawnMode === 'shared') {
|
|
352
|
+
readySharedQueues.push(group.queueName)
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
253
355
|
spawnQueueWorker(group.queueName)
|
|
254
356
|
}
|
|
255
357
|
}
|
|
358
|
+
if (spawnMode === 'shared' && readySharedQueues.length > 0) {
|
|
359
|
+
spawnSharedWorker(readySharedQueues)
|
|
360
|
+
}
|
|
256
361
|
}
|
|
257
362
|
|
|
258
363
|
function scheduleNext(): void {
|
package/src/mercato.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { getRedisUrl, getRedisUrlOrThrow } from '@open-mercato/shared/lib/redis/
|
|
|
11
11
|
import { resolveInitDerivedSecrets } from './lib/init-secrets'
|
|
12
12
|
import {
|
|
13
13
|
resolveAutoSpawnWorkersMode,
|
|
14
|
+
resolveLazySpawnMode,
|
|
14
15
|
resolveLazyPollMs,
|
|
15
16
|
resolveLazyRestart,
|
|
16
17
|
} from './lib/auto-spawn-workers'
|
|
@@ -2119,7 +2120,11 @@ export async function run(argv = process.argv) {
|
|
|
2119
2120
|
if (discoveredWorkerQueues.length === 0) {
|
|
2120
2121
|
console.error('[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.')
|
|
2121
2122
|
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
2122
|
-
|
|
2123
|
+
const lazySpawnMode = resolveLazySpawnMode(process.env)
|
|
2124
|
+
const lazyModeHint = lazySpawnMode === 'shared'
|
|
2125
|
+
? 'shared worker mode — one `queue worker --all` process; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=per-queue to change'
|
|
2126
|
+
: 'per-queue worker mode — one process per queue; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=shared to change'
|
|
2127
|
+
console.log(`[server] Lazy worker auto-spawn enabled — workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched, ${lazyModeHint}).`)
|
|
2123
2128
|
activeLazySupervisor = startLazyWorkerSupervisor({
|
|
2124
2129
|
mercatoBin,
|
|
2125
2130
|
appDir,
|
|
@@ -2127,6 +2132,7 @@ export async function run(argv = process.argv) {
|
|
|
2127
2132
|
workers: discoveredWorkers,
|
|
2128
2133
|
pollMs: resolveLazyPollMs(process.env),
|
|
2129
2134
|
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
2135
|
+
spawnMode: lazySpawnMode,
|
|
2130
2136
|
})
|
|
2131
2137
|
} else {
|
|
2132
2138
|
console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
|
|
@@ -2324,7 +2330,11 @@ export async function run(argv = process.argv) {
|
|
|
2324
2330
|
if (discoveredWorkerQueues.length === 0) {
|
|
2325
2331
|
console.error('[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.')
|
|
2326
2332
|
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
2327
|
-
|
|
2333
|
+
const lazySpawnMode = resolveLazySpawnMode(process.env)
|
|
2334
|
+
const lazyModeHint = lazySpawnMode === 'shared'
|
|
2335
|
+
? 'shared worker mode — one `queue worker --all` process; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=per-queue to change'
|
|
2336
|
+
: 'per-queue worker mode — one process per queue; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=shared to change'
|
|
2337
|
+
console.log(`[server] Lazy worker auto-spawn enabled — workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched, ${lazyModeHint}).`)
|
|
2328
2338
|
activeLazySupervisor = startLazyWorkerSupervisor({
|
|
2329
2339
|
mercatoBin,
|
|
2330
2340
|
appDir,
|
|
@@ -2332,6 +2342,7 @@ export async function run(argv = process.argv) {
|
|
|
2332
2342
|
workers: discoveredWorkers,
|
|
2333
2343
|
pollMs: resolveLazyPollMs(process.env),
|
|
2334
2344
|
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
2345
|
+
spawnMode: lazySpawnMode,
|
|
2335
2346
|
})
|
|
2336
2347
|
} else {
|
|
2337
2348
|
console.log('[server] Eager worker auto-spawn enabled - starting workers for all queues...')
|