1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-06-20 22:54:05 +00:00

Simplify concurrent pull test

Remove the worker solution, simulate the concurrent pull by updating pending_since while PullingPendingTenant
This commit is contained in:
lukinovec 2026-06-12 10:14:10 +02:00
parent 0995a073c2
commit 4b989d87b0
2 changed files with 18 additions and 85 deletions

View file

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
/**
* Worker for the "concurrent pulls" tests.
* Separate OS process that boots the same test env and calls pullPendingFromPool().
* Since multiple processes run at once, they race for the pool.
*
* Used like `php pull-worker.php <startAtUnixFloat> <firstOrCreate:0|1>`
*
* Outputs the key of the pulled tenant, or "null" if nothing was pulled.
*/
require __DIR__ . '/../../vendor/autoload.php';
use Stancl\Tenancy\Tests\Etc\Tenant;
use Stancl\Tenancy\Tests\TestCase;
$startAt = (float) ($argv[1] ?? 0);
$firstOrCreate = ($argv[2] ?? '0') === '1';
// createApplication() replays the suite's central-MySQL config without running setUp(),
// so the pending tenants the parent test created survive into this process.
(new class('pull-worker') extends TestCase {})->createApplication();
// Wait so that every worker pulls at the same time
if ($startAt > 0.0) {
time_sleep_until($startAt);
}
$tenant = Tenant::pullPendingFromPool($firstOrCreate);
fwrite(STDOUT, $tenant?->getKey() ?? 'null');

View file

@ -29,7 +29,6 @@ use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyEnded; use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Symfony\Component\Process\Process;
beforeEach($cleanup = function () { beforeEach($cleanup = function () {
Tenant::$extraCustomColumns = []; Tenant::$extraCustomColumns = [];
@ -128,63 +127,31 @@ test('a new tenant gets created while pulling a pending tenant if the pending po
expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants
}); });
/** test('pulling a pending tenant retries when the tenant is claimed concurrently', function () {
* Spawn $count separate PHP processes that all call pullPendingFromPool() at the same Tenant::createPending();
* time and return the keys of pulled tenants to simulate concurrent pulls. Tenant::createPending();
*
* @see tests/Etc/pull-worker.php
*/
function runConcurrentPulls(int $count, bool $firstOrCreate = false): array
{
$worker = __DIR__ . '/Etc/pull-worker.php';
// Shared start instant $stolenId = null;
$startAt = (string) (microtime(true) + 3.0);
/** @var Process[] $processes */ Event::listen(PullingPendingTenant::class, function (PullingPendingTenant $event) use (&$stolenId) {
$processes = []; if ($stolenId !== null) {
return;
for ($i = 0; $i < $count; $i++) {
$process = new Process(
['php', $worker, $startAt, $firstOrCreate ? '1' : '0']
);
$process->start();
$processes[] = $process;
}
$pulledTenants = [];
foreach ($processes as $process) {
$process->wait();
expect($process->isSuccessful())->toBeTrue($process->getErrorOutput());
$output = trim($process->getOutput());
if ($output !== 'null' && $output !== '') {
// If a tenant was pulled, add its key to the results
$pulledTenants[] = $output;
} }
}
return $pulledTenants; $stolenId = $event->tenant->id;
}
test('concurrent pulls each claim a distinct pending tenant', function (bool $firstOrCreate) { // Steal the tenant like a concurrent process would
Tenant::createPending(); Tenant::onlyPending()
Tenant::createPending(); ->whereKey($event->tenant->id)
Tenant::createPending(); ->update([$event->tenant->getColumnForQuery('pending_since') => null]);
});
expect(Tenant::onlyPending()->count())->toBe(3); $pulled = Tenant::pullPendingFromPool();
runConcurrentPulls(8, $firstOrCreate); expect($pulled)->not()->toBeNull();
expect($pulled->id)->not()->toBe($stolenId); // Stolen tenant was skipped, the next one was claimed by the pull
expect(Tenant::onlyPending()->count())->toBe(0); expect(Tenant::onlyPending()->count())->toBe(0); // Both tenants claimed
expect(Tenant::withoutPending()->count())->toBe($firstOrCreate ? 8 : 3); });
})->with([
'pull pending' => false,
'pull pending or create' => true,
]);
test('a failed attribute write rolls back the claim and leaves the tenant pending', function () { test('a failed attribute write rolls back the claim and leaves the tenant pending', function () {
// The claim and the attribute write share one transaction, so if applying the attributes // The claim and the attribute write share one transaction, so if applying the attributes