Repository navigation
fix(sync): stream JSONL sources in bounded batches and ship external feeds disabled - #2690
Merged
Merged
Conversation
…feeds disabled A fresh install enabled 18 jobs that pull external data. Austria's OpenTender full.jsonl.gz (451 MB, 181,741 records decompressed) was read whole: call log body, gunzipped string and every record in one array. Replayed against the real file under a 2 GB limit it dies at about 90,000 records in json_decode, the fatal seen in cron.php. - JSONL downloads stream to a temp file through the call engine's sink and are read line by line with gzopen/gzgets. - One run reads one batch (sourceConfig.pageSize, default 5000), resumed through currentPage; only a whole file read in one run is a complete fetch, so a batch never triggers the deletion pass. - A run with batches left returns resumeSoon and the job runs on the next tick. - The ten external data-gathering connector jobs ship disabled (opt-in). - OpenSpec change bounded-jsonl-sync.
rubenvdlinde
requested review from
Rem-Dam,
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
October 16, 2026 10:14
Contributor
Quality Report — ConductionNL/integriq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 143/143 | |||
| npm | ✅ | ✅ 646/646 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ 5499/5499 · 1 skipped | ||||
| Newman | ✅ 130/130 | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development. |
||||
| Hydra gates | ✅ | ||||
| Hydra gates (axe) | ✅ |
Quality workflow — 2026-10-16 10:20 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What broke
Rehearsal R3 (fresh NC 32.0.13, integriq 0.4.12-unstable.20261015175726):
cron.phpexited 255 on 2 of 5 runs,Allowed memory size of 2147483648 bytes exhaustedinSynchronizationService::parseJsonLines().Cause confirmed: a fresh install enables 18 jobs that pull external data. One reads Austria's OpenTender
full.jsonl.gz(70 MB gzip, 451 MB and 181,741 records decompressed). The JSONL path held the body whole three times (base64 call log body, gunzipped string, every record in one array). Replaying that exact path against the real file undermemory_limit=2048Mdies at about 90,000 records in thejson_decodeline, the same line as the cron fatal.What changes
format: "jsonl"synchronization streams the download to a temp file through the call engine's existingsink, and reads it line by line (gzopen/gzgets, gzip or plain). The temp file is removed after the batch.sourceConfig.pageSizerecords (default 5,000), resumed through the existingcurrentPage. Only a whole file read in one run is a complete fetch; every other batch is incomplete (batch_limit_reached), so the deletion pass never runs on part of the source.resume;SynchronizationActionreturnsresumeSoon, andJobServiceschedules that job on the next tick instead of after its interval.isEnabled: false. Sources, mappings and synchronizations still ship. Their in-flight spec (native-data-gathering-provider) already said a connector needing streaming of very large files ships disabled; its "live-verified may ship enabled" scenario now says the Job ships disabled.bounded-jsonl-sync(REQ-006 modified, job-scheduling requirement added); validates.Not changed, on purpose
endoflife-date-sourcespec requiresisEnabled: true. They are one small JSON call per product per day.JobServicecatches\Throwablearound each action and per job in the pass (pinned bytestRunProcessesAllThreeJobsWhenTheFirstThrows). Memory exhaustion is a fatal no handler sees, which is why the fix is the bounded reader.gzdecodefor gzip JSON) still parse one page whole; they are bounded by pagination,maxPagesand the existing memory ceiling.Verified
memory_limit=512M: a 5,000-record batch from the start peaks at 90 MB; from record 175,000 at 82 MB; the whole file is scanned in about 4 s.currentPagecarry, droppedresumeSoon,completeforced true all turn them red): stream to a removed temp file, batch and resume, whole-file run complete, failed download keeps its batch, a 24 MB file read under a 2 MB budget with a control proving the fixture exposes a whole-body reader, a batch run deletes nothing and persists the next batch,resumeSoonscheduling.composer check:strict(run once, phpmd re-run on the two refactored files after): lint, phpcs, psalm, phpstan, routes, legacy-types pass; phpmd clean after splittingfetchJsonLinesBatch; PHPUnit 5,500 tests, 11 errors, all intests/Unit/AppInfo/*(TypeError: Cannot assign MockObject_IAppContainer to property App::$container of type DIContainer), a local OCP-stub mismatch in DI tests this PR does not touch.Inherited: phpcs reports 313 findings in the touched test files on lines this PR does not change (tests are outside the
phpcs.xmlscope).