Commit INDEXED files opened OUTPUT at COMMIT and CLOSE instead of on every WRITE - #892
Open
yutaro-sakamoto wants to merge 10 commits into
Open
Conversation
codegen.c kept the inside_check/inside_stack bookkeeping from the original C code generator, but only the decrements survived the rewrite to Java code generation: nothing ever increments inside_check. On non-GCC builds (the MSVC-built cobj.exe) the counter goes negative as soon as a runtime check is attached to a reference (-debug with a variable subscript, reference modification or SEARCH ALL), and inside_stack[inside_check - 1] then writes out of bounds, crashing the compiler. GCC builds never compile these blocks, which is why Linux was unaffected. Remove the machinery entirely: the generated Java must not depend on the compiler that built cobj. Un-skip the Windows tests that were failing due to this crash. Fixes opensourcecobol#829, opensourcecobol#831, opensourcecobol#832, opensourcecobol#833, opensourcecobol#834 (sub-issues of opensourcecobol#828)
With -java-package the jar arguments were assembled as package_dir + file_path_delimitor + class, mixing the '/' separators from the package path with '\' on Windows (e.g. com/abc\prog.class), which produces malformed entry names in the archive. The jar tool accepts '/' everywhere, so join the paths with '/' unconditionally. The class file cleanup after archiving had the opposite problem: cmd.exe's del does not accept '/' separators. Build the remove paths with the native separator, let cd handle the output directory, and drop a leftover '#aaa' token that del would treat as an argument. Un-skip the Windows jar test. Fixes opensourcecobol#830 (sub-issue of opensourcecobol#828)
Removing the inside_check block exposed a variableScope finding for 'code' in joutput_stmt; declare it in the handler block where it is used.
Every program in a translation unit becomes its own .java/.class file named after the program ID, so two programs whose IDs differ only in letter case overwrite each other's files on a case-insensitive filesystem and the result silently misbehaves. On Windows, detect the collision before code generation and fail with an explicit error. The two affected tests stay skipped on Windows because the behavior they exercise is impossible there by design; a new Windows-only test verifies the diagnostic instead. Fixes opensourcecobol#835 (sub-issue of opensourcecobol#828)
On case-insensitive file systems programs whose PROGRAM-IDs differ only in letter case would generate class files with the same name; cobj now reports a compile error there, so document the restriction in the usage section.
* Use "cd /d" on Windows: cmd.exe's plain "cd" does not switch drives, so with -o pointing at another drive the jar and del commands would run against the wrong directory * Replace the unbounded strcpy into package_dir_native with snprintf and give package_name_to_path an explicit output bound, so an overlong -java-package argument can no longer overflow the buffers
yutaro-sakamoto
force-pushed
the
indexed-output-defer-commit-upstream
branch
2 times, most recently
from
August 19, 2026 08:08
9e664a2 to
b91cfac
Compare
…every WRITE An indexed file opened with OUTPUT holds an exclusive file lock and only WRITE statements run until CLOSE, so no other process can observe the intermediate states. Committing the JDBC transaction - and therefore fsyncing the SQLite database - after every WRITE is unnecessary there. The records are now committed by the COBOL COMMIT statement and at CLOSE. Writing 100,000 records takes 0.46s instead of 382.7s. COB_FILE_IDX_COMMIT_INTERVAL makes the runtime commit on its own every N successful WRITEs as well, which bounds what a crash before CLOSE can lose at N records in exchange for one fsync per interval. It defaults to INF (no intermediate commits) because COBOL has a COMMIT statement, so the commit points belong to the program. 0 is treated as 1 (commit per WRITE) and an invalid value falls back to the default with a warning. Duplicate-key detection is unaffected: uncommitted rows are visible to queries on the same connection, so WRITE still reports file status 21 and 22 immediately. Each WRITE of a file with alternate keys runs inside a savepoint so that a failed WRITE rolls back only its own changes; files with no alternate key need no savepoint because their WRITE is a single atomic INSERT. Supporting changes: - exitFileIO now really closes the files it warns about. It only printed "WARNING - Implicit CLOSE" before, so a program reaching STOP RUN without CLOSE lost the deferred records and left a stale file_lock row that made every later OPEN fail with status 61. - unlock_ releases this process's record locks instead of printing "Unlocking INDEXED file is not implemented", so UNLOCK, COMMIT and ROLLBACK work on indexed files. The file_lock row is deliberately kept until CLOSE, since it doubles as the open registration. - ROLLBACK is dispatched through a rollback_ hook. For a file open OUTPUT it discards the writes that are still uncommitted; in the other modes every statement is already committed, so it releases the record locks only, as cob_rollback does in opensource COBOL 1.x. - SQL failures on the WRITE path used to be reported as status 51 (record locked) whatever went wrong. They now map to 34 for disk full, 22 for a constraint violation, 51 for lock contention and 30 otherwise. CLOSE reports 30 when a failed WRITE may have taken the buffered records with it, since SQLite rolls back the whole transaction for conditions such as disk full. - The file cache keeps only open files, and the INSERT statements are prepared once per table instead of on every WRITE.
yutaro-sakamoto
force-pushed
the
indexed-output-defer-commit-upstream
branch
from
August 19, 2026 09:13
b91cfac to
960dce0
Compare
yutaro-sakamoto
marked this pull request as ready for review
August 19, 2026 11:55
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.
Note
CIを通すために、PR #862(Windows向けの
cobj.exe修正)をマージしたうえで変更を積んでいます。#862がdevelopに入った後、このブランチはrebaseします。概要
OPEN OUTPUTした索引編成ファイルに対して、WRITEのたびに行っていたJDBCトランザクションのコミット(=SQLiteのfsync)をやめました。レコードはCOBOLのCOMMIT文とCLOSE時にコミットされます。80バイトのレコードを100,000件書き込む処理が 382.7秒 → 0.46秒 になります。OPEN OUTPUTした索引編成ファイルはfile_lockテーブルによりファイル全体が排他ロックされ(他プロセスのOPENはファイルステータス61で拒否)、CLOSEまでに実行されるのはWRITEだけです。したがって中間状態が他プロセスから観測されることはなく、コミットを遅延しても共有の挙動は変わりません。環境変数
COB_FILE_IDX_COMMIT_INTERVALランタイム側でも定期的にコミットさせたい場合に指定します。
INF(大文字小文字不問)WRITEがN件たまるごとにもコミットする01と同じ(WRITEのたびにコミット)デフォルトを
INFにしたのは、COBOLにはCOMMIT文があり、コミットの時点はプログラムがCOMMIT文とCLOSEで決めるのが本来の姿だと考えたためです。整数を指定すると、CLOSE前にプロセスがクラッシュ・強制終了した場合に失われるレコードをその件数までに抑えられます(コミット1回あたり約4msのfsyncコストと引き換え)。doc/environment_variables.mdとdoc/environment_variables_JP.mdにも記載しています。動作の要点
WRITEのたびに行われます。 同一コネクション上のクエリからは未コミットの行も見えるため、ファイルステータス21・22は即座に報告されます。WRITEをセーブポイントで囲みます。 単純にconnection.rollback()すると、遅延中の成功済みWRITEまで巻き戻ってしまうためです。副キーを持たないファイルのWRITEは単一のINSERTで、部分的な変更が残る失敗が構造的に存在しないためセーブポイントを省略しています。INPUT・I-O・EXTENDで開いたファイルの挙動は変わりません。併せて修正した関連箇所
exitFileIOが実際にクローズするようにしました。 従来は「WARNING - Implicit CLOSE」を表示するだけで実際にはクローズしておらず、CLOSEを忘れたままSTOP RUNに到達すると遅延レコードが失われるうえ、file_lockの行が残って以後のOPENがすべてファイルステータス61で拒否される状態でした。unlock_を実装しました。 従来は「Unlocking INDEXED file is not implemented」を出力するだけでした。現在は自プロセスが保持するレコードロックを解放するので、UNLOCK文・COMMIT文・ROLLBACK文が索引編成ファイルでも機能します。ただしfile_lockの行はオープン中である登録を兼ねているため、CLOSEまで解放しません(先に消すと他プロセスのOPEN OUTPUTがテーブルを作り直せてしまいます)。ROLLBACK文をrollback_フックとして実装しました。OPEN OUTPUTのファイルでは未コミットのWRITEを実際に取り消します(順序チェックの基準キーも消すので、同じキーから書き直せます)。それ以外のモードでは各文がその場でコミット済みで取り消せる変更がないため、opensource COBOL 1.xのcob_rollbackと同じくレコードロックの解放だけを行います。SQLExceptionを一律51(レコードロック)にしていましたが、ディスクフルは34、一意制約違反は22、ロック競合は51、その他は30に対応付けます。またSQLiteはディスクフル等では文単位ではなくトランザクション全体を巻き戻すため、1件のWRITEエラーの裏でバッファ中のレコードがすべて失われることがあります。この場合CLOSEは30を返し、成功を装いません。PreparedStatementはテーブルごとに1つ生成して使い回すようにしました。性能
80バイトのレコードをN件連続
WRITEするプログラムのwall time(JVM起動時間 約0.15秒を含む)。環境: WSL2 (Linux 5.15) + ext4、中央値/3回。INF)100,000件書き込んだファイルを
cobj-idx unloadし、100,000行すべてが正しいキー順で格納されていることを確認しています。テスト
tests/misc.src/indexed-output-commit-interval.atを追加しました(8グループ)。OPEN OUTPUTでの重複主キー(22)・重複ユニーク副キー(22)・キー順序違反(21)がWRITE時点で報告され、エラー後も書き込み済みレコードが失われないことACCESS SEQUENTIALでの21と22の優先順位(001,003の順に書いた後の002や001は、重複ではなく順序違反の21。22になるのは直前のキーと等しい場合だけ)がindexed_writeの実装と一致することCOB_FILE_IDX_COMMIT_INTERVALを0/1/3/10/1000000/INF/infと変えても読み戻した結果が完全に一致すること、不正値は警告を出してデフォルトにフォールバックすることCLOSEせずにSTOP RUNしても暗黙クローズで全件コミットされることOPEN EXTENDでのWRITEのファイルステータスと追記結果COMMIT文が遅延中のレコードをコミットすることROLLBACK文がOPEN OUTPUTの未コミット分を取り消し、同じキーを書き直せることREWRITEやCOMMIT文の後)はROLLBACKで取り消されないことSummary (English)
An indexed file opened with
OUTPUTno longer commits the backing JDBC transaction — and therefore fsyncs the SQLite database — after everyWRITE. The records are committed by the COBOLCOMMITstatement and atCLOSE. Writing 100,000 80-byte records takes 0.46s instead of 382.7s.Such a file holds an exclusive lock through the
file_locktable (any otherOPENgets file status 61) and onlyWRITEstatements run untilCLOSE, so no other process can observe the intermediate states and deferring the commits does not change the sharing behaviour.Environment variable
COB_FILE_IDX_COMMIT_INTERVALINF(case-insensitive)WRITEs01(commit on everyWRITE)INFis the default because COBOL has aCOMMITstatement, so the commit points belong to the program. Setting an integer bounds what a crash beforeCLOSEcan lose at that number of records, in exchange for one fsync per interval. Documented indoc/environment_variables.mdanddoc/environment_variables_JP.md.Key points
WRITE: uncommitted rows are visible to queries on the same connection, so file status 21 and 22 are reported immediately.WRITEof a file with alternate keys runs inside a savepoint, so a failedWRITErolls back only its own changes instead of the whole buffer. A file with no alternate key needs none, because itsWRITEis a single atomic INSERT.INPUT,I-OorEXTENDbehave as before.Related fixes
exitFileIOnow really closes the files it warns about. It only printed "WARNING - Implicit CLOSE" before, so a program reachingSTOP RUNwithoutCLOSElost the deferred records and left a stalefile_lockrow that made every laterOPENfail with status 61.unlock_is implemented instead of printing "Unlocking INDEXED file is not implemented", soUNLOCK,COMMITandROLLBACKwork on indexed files. Thefile_lockrow is deliberately kept untilCLOSE, since it doubles as the registration that the file is open.ROLLBACKis dispatched through arollback_hook. For a file openOUTPUTit discards the writes that are still uncommitted (and forgets the last key, so the same keys can be written again); in the other modes every statement is already committed, so it releases the record locks only, exactly likecob_rollbackin opensource COBOL 1.x.SQLExceptionused to become 51 (record locked); disk full is now 34, a constraint violation 22, lock contention 51 and anything else 30. SQLite also rolls back the whole transaction rather than the failing statement for conditions such as disk full, so a single failedWRITEcan take the buffered records with it —CLOSEreturns 30 in that case rather than reporting success.WRITE.Benchmark
Wall time of a program that sequentially
WRITEs N 80-byte records (including ~0.15s of JVM startup). WSL2 (Linux 5.15) on ext4, median of 3 runs.INF)The 100,000-record file was unloaded with
cobj-idx unloadto confirm that all 100,000 records are present in key order.Tests
tests/misc.src/indexed-output-commit-interval.atadds eight groups covering theWRITEstatuses inOUTPUTandEXTENDmode, the precedence between status 21 and 22 forACCESS SEQUENTIAL, the invariance of the written records across everyCOB_FILE_IDX_COMMIT_INTERVALvalue, the implicit close atSTOP RUN, and theCOMMITandROLLBACKstatements.