Skip to content

Commit 2a7f9b3

Browse files
committed
Reuse primary PHP also for queued requests, reuse secondary PHP insances
1 parent 3b02f72 commit 2a7f9b3

File tree

4 files changed

+132
-209
lines changed

4 files changed

+132
-209
lines changed

packages/php-wasm/node/src/test/php-instance-manager.spec.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe('PHPProcessManager', () => {
159159
);
160160
});
161161

162-
it('should not start a second PHP instance until the first getInstance() call when the primary instance is busy', async () => {
162+
it('should reuse idle instances and only spawn when needed', async () => {
163163
const phpFactory = vitest.fn(
164164
async () => new PHP(await loadNodeRuntime(RecommendedPHPVersion))
165165
);
@@ -169,39 +169,45 @@ describe('PHPProcessManager', () => {
169169
});
170170

171171
expect(phpFactory).not.toHaveBeenCalled();
172+
173+
// First acquire spawns primary instance
172174
const php1 = await mgr.acquirePHPInstance();
173175
expect(phpFactory).toHaveBeenCalledTimes(1);
174176
php1.reap();
175177

178+
// Second acquire reuses the now-idle primary
176179
const php2 = await mgr.acquirePHPInstance();
177180
expect(phpFactory).toHaveBeenCalledTimes(1);
178181
php2.reap();
179182

180-
await mgr.acquirePHPInstance();
181-
await mgr.acquirePHPInstance();
182-
expect(phpFactory).toHaveBeenCalledTimes(3);
183+
// Third acquire reuses primary again
184+
const php3 = await mgr.acquirePHPInstance();
185+
expect(phpFactory).toHaveBeenCalledTimes(1);
186+
187+
// Fourth acquire needs a new instance (primary is busy)
188+
const php4 = await mgr.acquirePHPInstance();
189+
expect(phpFactory).toHaveBeenCalledTimes(2);
190+
191+
php3.reap();
192+
php4.reap();
183193
});
184194

185-
it('should refuse to spawn two primary PHP instances', async () => {
195+
it('should not spawn duplicate primary instances when called concurrently', async () => {
196+
const phpFactory = vitest.fn(
197+
async () => new PHP(await loadNodeRuntime(RecommendedPHPVersion))
198+
);
186199
const mgr = new PHPProcessManager({
187-
phpFactory: async () =>
188-
new PHP(await loadNodeRuntime(RecommendedPHPVersion)),
200+
phpFactory,
189201
maxPhpInstances: 5,
190202
});
191203

192-
// A synchronous call. Do not await this promise on purpose.
193-
mgr.getPrimaryPhp();
194-
195-
// No await here, because we want to check if a second,
196-
// synchronous call throws an error if issued before
197-
// the first call completes asynchronously.
198-
try {
199-
mgr.getPrimaryPhp();
200-
} catch (e) {
201-
expect(e).toBeInstanceOf(Error);
202-
expect((e as Error).message).toContain(
203-
'Requested spawning a primary PHP instance'
204-
);
205-
}
204+
// Call getPrimaryPhp() twice concurrently - both should return the same instance
205+
const [php1, php2] = await Promise.all([
206+
mgr.getPrimaryPhp(),
207+
mgr.getPrimaryPhp(),
208+
]);
209+
210+
expect(php1).toBe(php2);
211+
expect(phpFactory).toHaveBeenCalledTimes(1);
206212
});
207213
});

packages/php-wasm/node/src/test/php-process-manager.spec.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('PHPProcessManager', () => {
7373
);
7474
});
7575

76-
it('should not start a second PHP instance until the first getInstance() call when the primary instance is busy', async () => {
76+
it('should reuse idle instances and only spawn when needed', async () => {
7777
const phpFactory = vitest.fn(
7878
async () => new PHP(await loadNodeRuntime(RecommendedPHPVersion))
7979
);
@@ -83,39 +83,45 @@ describe('PHPProcessManager', () => {
8383
});
8484

8585
expect(phpFactory).not.toHaveBeenCalled();
86+
87+
// First acquire spawns primary instance
8688
const php1 = await mgr.acquirePHPInstance();
8789
expect(phpFactory).toHaveBeenCalledTimes(1);
8890
php1.reap();
8991

92+
// Second acquire reuses the now-idle primary
9093
const php2 = await mgr.acquirePHPInstance();
9194
expect(phpFactory).toHaveBeenCalledTimes(1);
9295
php2.reap();
9396

94-
await mgr.acquirePHPInstance();
95-
await mgr.acquirePHPInstance();
96-
expect(phpFactory).toHaveBeenCalledTimes(3);
97+
// Third acquire reuses primary again
98+
const php3 = await mgr.acquirePHPInstance();
99+
expect(phpFactory).toHaveBeenCalledTimes(1);
100+
101+
// Fourth acquire needs a new instance (primary is busy)
102+
const php4 = await mgr.acquirePHPInstance();
103+
expect(phpFactory).toHaveBeenCalledTimes(2);
104+
105+
php3.reap();
106+
php4.reap();
97107
});
98108

99-
it('should refuse to spawn two primary PHP instances', async () => {
109+
it('should not spawn duplicate primary instances when called concurrently', async () => {
110+
const phpFactory = vitest.fn(
111+
async () => new PHP(await loadNodeRuntime(RecommendedPHPVersion))
112+
);
100113
const mgr = new PHPProcessManager({
101-
phpFactory: async () =>
102-
new PHP(await loadNodeRuntime(RecommendedPHPVersion)),
114+
phpFactory,
103115
maxPhpInstances: 5,
104116
});
105117

106-
// A synchronous call. Do not await this promise on purpose.
107-
mgr.getPrimaryPhp();
108-
109-
// No await here, because we want to check if a second,
110-
// synchronous call throws an error if issued before
111-
// the first call completes asynchronously.
112-
try {
113-
mgr.getPrimaryPhp();
114-
} catch (e) {
115-
expect(e).toBeInstanceOf(Error);
116-
expect((e as Error).message).toContain(
117-
'Requested spawning a primary PHP instance'
118-
);
119-
}
118+
// Call getPrimaryPhp() twice concurrently - both should return the same instance
119+
const [php1, php2] = await Promise.all([
120+
mgr.getPrimaryPhp(),
121+
mgr.getPrimaryPhp(),
122+
]);
123+
124+
expect(php1).toBe(php2);
125+
expect(phpFactory).toHaveBeenCalledTimes(1);
120126
});
121127
});

0 commit comments

Comments
 (0)