Files
SGF/scripts/stock-ledger-race.php
T
2026-11-04 09:37:20 +02:00

32 lines
4.5 KiB
PHP

<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
if($phase==='worker'){
$file=realpath($argv[3]??'');if(!$file||!str_starts_with($file,realpath($s['root']).DIRECTORY_SEPARATOR)||file_exists($file.'.result.json'))throw new LogicException('Fresh owned worker input required');$input=json_decode(file_get_contents($file),true,32,JSON_THROW_ON_ERROR);
DB::select("SELECT set_config('application_name',?,false)",[$input['application']]);DB::statement("SET lock_timeout='10s'");
try{$r=$command('prepare','post',$input['body'],$input['key']);$out=['status'=>200,'result'=>$r];}catch(DomainException $e){$out=['status'=>409,'message'=>$e->getMessage()];}
file_put_contents($file.'.result.json',json_encode($out,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));exit;
}
if(file_exists($s['root'].'/race.json')||DB::table('gl_journals')->exists()||DB::table('gl_stock_rates')->exists())throw new LogicException('Fresh race phase only; completed state preserved');
$before=$fingerprint();$rate();$results=[];
$prepare=function($m)use($command,$body){$p=$command('prepare','effect-prepare',$body($m));$command('validate','validate',['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'decision'=>'approved','reason'=>'Distinct source conference before controlled concurrent application.']);return ['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'reason'=>'One reviewed application after deliberate lock release.'];};
foreach(['same_key_commit','period_changed_then_refuse']as$index=>$scenario){
$d=$prepare($f['movements'][$index]);$key=(string)Str::uuid();$input=$s['root'].'/race-'.$scenario.'.json';$application='stock_ledger_'.$s['tag'].'_'.$index;
file_put_contents($input,json_encode(['body'=>$d,'key'=>$key,'application'=>$application],JSON_THROW_ON_ERROR));$process=null;DB::beginTransaction();
try{
DB::table('tenants')->where('id',$scope['tenant_id'])->lockForUpdate()->firstOrFail();
if($index===0)$winner=$command('prepare','post',$d,$key);else{ $period=(array)DB::table('gl_periods')->orderByDesc('version')->first();unset($period['id']);$period['version']=2;$period['reason']='Explicit same-period new version during pending confirmation.';DB::table('gl_periods')->insert($period);}
// Settings path is fixed to this run, never the worker input path.
$process=new Symfony\Component\Process\Process([PHP_BINARY,__DIR__.'/stock-ledger-entry.php',$s['root'].'/settings.json','worker',$input],dirname(__DIR__),null,null,30);$process->start();
$parent=(int)DB::selectOne('SELECT pg_backend_pid() pid')->pid;$wait=null;$deadline=microtime(true)+10;
do{DB::select('SELECT pg_stat_clear_snapshot()');$wait=DB::selectOne("SELECT wait_event,to_json(pg_blocking_pids(pid)) blockers FROM pg_stat_activity WHERE application_name=? AND wait_event_type='Lock'",[$application]);if(!$wait)usleep(25000);}while(!$wait&&microtime(true)<$deadline);
if(!$wait||!in_array($parent,json_decode($wait->blockers,true),true))throw new RuntimeException('Expected physical company lock was not observed');DB::commit();$process->wait();file_put_contents($s['root'].'/race-'.$scenario.'.log',$process->getOutput().$process->getErrorOutput());
if(!$process->isSuccessful()||!file_exists($input.'.result.json'))throw new RuntimeException('Concurrent worker incomplete');$r=json_decode(file_get_contents($input.'.result.json'),true,32,JSON_THROW_ON_ERROR);
if($index===0){$ok($r['status']===200&&$r['result']['replayed']&&$r['result']['journal_id']===$winner['journal_id'],'released same-key confirmation returns original journal without duplication');}
else{$ok($r['status']===409&&!DB::table('gl_commands')->where('command_key',$key)->exists(),'released worker rechecks changed period and refuses stale capture without receipt');}
$results[]=['scenario'=>$scenario,'observed_wait'=>$wait->wait_event,'blocker_observed'=>true,'status'=>$r['status'],'journals_after'=>DB::table('gl_journals')->count()];
}finally{while(DB::transactionLevel()>0)DB::rollBack();if($process&&$process->isRunning())$process->stop(1);}
}
$ok(DB::table('gl_journals')->count()===1&&DB::table('gl_lines')->count()===2&&$before===$fingerprint(),'two workers leave one journal and original physical/financial/rights state');
file_put_contents($s['root'].'/race.json',json_encode(['passed'=>true,'checks'=>$checks,'workers'=>$results,'source_fingerprint_unchanged'=>true,'not_throughput_measurement'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Physical company lock observed; released replay200 and stale-period409, one journal.\n";