おい、最近の若手は便利なオープン系の言語に慣れているせいか、メインフレームの足回りで何か予期せぬ異常が発生すると、すぐに「 Abend(異常終了)しました!」と青い顔をして泣きついてくる。だがな、我々が扱う基幹システムにおいて、夜間バッチの途中でシステムが突然死することほど恐ろしいものはない。ジョブネット全体が崩壊し、リカバリにどれだけの工数がかかることか……。
だからこそ、エラーをただの「クラッシュ」で終わらせず、プログラム自身で捕縛し、优雅に、かつ確実に制御するための仕組みが必要になる。それが、IBM Language Environment(以下、LE)が提供する `CEEHDLR`(ユーザー定義エラーハンドラの登録) だ。
今回は、PL/Iの伝統的な `ON条件` によるエラー処理の限界を突破し、LEのスタックフレームに独自の例外処理ルーチンをダイナミックに噛み合わせる、ワンランク上のエラー制御技術を伝授しよう。
—
1. なぜ従来のONユニットだけでは足りないのか?
PL/Iプログラマなら誰しも、`ON ERROR` や `ON FIXFORG` といった古典的なONユニットでお世話になったことがあるはずだ。あれはあれで直感的で素晴らしい機能だが、大規模なサブシステムや、複数言語(COBOLやCなど)が混在する現代のメインフレーム環境では、いくつか厄介な制約にぶぶつかる。
- スタックを跨いだ広域ハンドリングの限界: 呼び出し階層の深くで発生した例外を、上位の特定の位置で一括して綺麗にキャッチし、リソースのクリーンアップを行ってから処理を継続させるのが難しい。
- 環境依存の例外への対応: ゼロ割り(`ZERODIVIDE`)やデータ例外(`DATATYPE`)だけでなく、LEが管理するシグナルをより低レベルで、かつ統一的にフックしたい。
ここで登場するのが、LEのサービスルーチンである `CEEHDLR`(Condition Handler Register) だ。これを使うと、プログラムの実行中に動的にエラーハンドラ(コールバック関数のようなもの)を登録し、LEの例外処理フローに直接割り込むことができる。
—
2. `CEEHDLR` の基本メカニズムと制御フロー
`CEEHDLR` を用いたエラーハンドリングの流れは、大体以下のようになる。
1. ハンドラ(サブルーチン)の作成: 例外が発生したときに呼び出される特定のインターフェースを持ったPL/I外部プロシージャ(または内部プロシージャ)を記述する。
2. 登録(`CEEHDLR` の呼び出し): メイン処理や初期化ルーチンの中で、`CEEHDLR` に自作ハンドラのポインタ(`ENTRY`変数)を渡してLEに登録する。
3. 例外発生: 計算エラーや入出力エラー、あるいは明示的なシグナル(`CEESGNL`など)が発生。
4. ハンドラの起動: LEはスタックフレームを遡り、登録されたユーザーハンドラを呼び出す。ハンドラはエラーの深刻度を判断し、「修正して処理を続行(Resume)」するか、「異常終了を継続(Percolate)」するかをLEに指示する。
5. 登録解除(`CEEDLRM` の呼び出し): プログラムの正常終了時、あるいはスコープを抜ける前に必ずハンドラの登録を解除する。これをサボると、最悪の場合ストレージの破損やLEの混乱を招くので注意しろ。
—
3. 実践! `CEEHDLR` 活用PL/Iサンプルコード
百聞は一見にしかずだ。実際にVSAMファイルからのレコード読み込みと数値演算を含むバッチ処理の中で、`CEEHDLR` を使って動的にエラーを捕捉・制御する実用的なコード例を示そう。
1
/================================================================/
/ プログラム名: CEEEXAMP /
/ テーマ: CEEHDLRによる動的エラーハンドラの登録とVSAM処理制御 /
/================================================================/
CEEXAMP: PROC OPTIONS(MAIN);
/ — 組み込み関数およびLEサービスの宣言 — /
DCL CEEHDLR ENTRY(ENTRY, POINTER, FIXED BIN(31));
DCL CEEDLRM ENTRY(ENTRY, FIXED BIN(31));
DCL CEE3ABD ENTRY(FIXED BIN(31), FIXED BIN(31));
DCL ADDR BUILTIN;
DCL NULL BUILTIN;
/ — ワーキングストレージ変数の定義 — /
DCL WS_RET_CODE FIXED BIN(31) INIT(0);
DCL WS_TOKEN POINTER INIT(NULL());
DCL WS_COUNTER FIXED BIN(31) INIT(0);
DCL WS_DIVISOR FIXED BIN(31) INIT(0); / ア゙ヷー゙! デズゼロの原因を作る /
DCL WS_RESULT FIXED BIN(31) INIT(0);
/ — VSAMファイル(KSDS)定義 — /
DCL VSAM_FILE FILE RECORD SEQUENTIAL INPUT
ENV(VSAM);
DCL 1 VSAM_REC,
5 REC_KEY CHAR(8),
5 REC_DATA CHAR(72);
DCL IO_EOF BIT(1) INIT(‘0’B);
ON ENDFILE(VSAM_FILE) IO_EOF = ‘1’B;
DISPLAY(‘ CEEEXAMP: プログラム開始 ‘);
/ 1. ユーザー定義エラーハンドラをLEのスタックフレームに登録 /
CALL CEEHDLR(MY_ERROR_HANDLER, WS_TOKEN, WS_RET_CODE);
IF WS_RET_CODE ^= 0 THEN DO;
DISPLAY(‘エラー: エラーハンドラの登録に失敗しました。’);
CALL CEE3ABD(3000, 3); / 異常終了 /
END;
/ 2. VSAMファイルのオープン /
OPEN FILE(VSAM_FILE);
/ 3. メインループ: レコード読み込みとあえてのゼロ割りテスト /
READ FILE(VSAM_FILE) INTO(VSAM_REC);
DO WHILE (^IO_EOF);
WS_COUNTER = WS_COUNTER + 1;
/ わざと 3回目で 0 による割算を引き起こす(テスト用) /
IF WS_COUNTER = 3 THEN
WS_DIVisOR = 0;
ELSE
WS_DIVISOR = 2;
/ ココで ZERODIVIDE データ例外が発生する可能性あり /
WS_RESULT = 100 / WS_DIVISOR;
DISPLAY(‘処理中: カウンタ=’ || TRIM(WS_COUNTER) || ‘ 結果=’ || TRIM(WS_RESULT));
READ FILE(VSAM_FILE) INTO(VSAM_REC);
END;
CLOSE FILE(VSAM_FILE);
/ 4. プログラム終了前のハンドラ登録解除(※超重要!) /
CALL CEEDLRM(MY_ERROR_HANDLER, WS_RET_CODE);
DISPLAY(‘ CEEEXAMP: プログラム正常終了 ‘);
RETURN;
/————————————————————/
/ ユーザー定義エラーハンドラ(LEから呼び出されるコールバックルーチン) /
/————————————————————/
MY_ERROR_HANDLER: PROC(CWI_COND, CWI_TOKEN, CWI_RESCT, CWI_FB);
DCL 1 CWI_COND, / ファーストパラーメータ: 条件ブロック /
5 C_SEVERITY FIXED BIN(16),
5 C_MSGNO FIXED BIN(16),
5 C_FLAGS BIT(32),
5 C_CASE CHAR(2),
5 C_FACID CHAR(3);
DCL CWI_TOKEN POINTER; /セカンドパラーメータ: ワーキングトーゲン /
DCL CWI_RESCT FIXED BIN(31); /サードパラーメータ: レスポンス/アクション /
DCL 1 CWI_FB, / フォースパラーメータ: フィードバックコード /
$FB_SEV FIXED BIN(16),
$FB_MSG FIXED BIN(16),
$FB_FLAGS BIT(32);
/ シグナルの深刻度やメッセージ番号を判定 /
DISPLAY(‘ エラーハンドラ起動: トラップされた例外を検知 ‘);
DISPLAY(‘メッセージ番号: ‘ || C_MSGNO);
/ ここでは例として、特定のエラー(例: ゼロ割り C_MSGNO = 321等) を捕捉した場合、
処理を安全に継続させるためのアクション(Resume)を指示する。
実務では、ここでダンプ採取やエラーログの出力、リソースのクリーンアップを行う。 /
IF C_MSGNO = 321 THEN DO; / ゙CEE3211S などのゼロ割り関連 /
DISPLAY(‘アラート: ゼロ割りを検知しましたが、ハンドラでポールドします。’);
/ 処理を継続させるためのフィードバックを返す設定 /
CWI_RESCT = 10; / 10 = Resume (処理を続行する指示) /
RETURN;
END;
/ その他の深刻なエラーは次のハンドラまたはシステム異常終了へ流す (Percolate) /
CWI_RESCT = OTHERS: 0; / 0 = Percolate (通常の異常終了へ移行) /
RETURN;
END MY_ERROR_HANDLER;
END CEEXAMP;
—
4. 保守フェーズにおけるデバッグと運用のコツ
現場でこの `CEEHDLR` を組み込んだバッチを運用・保守する際、ベテランからいくつかアドバイスを送っておこう。
1. CEEDLRM(登録解除)の消し忘れに殺意を持て:
サブルーチンやネストされたプロシージャの中で `CEEHDLR` を呼んだはいいが、プロシージャを抜ける時に `CEEDLRM` を呼び忘れると、LEのスタック管理が狂い、後続の全く関係ない処理で奇怪なストレージ違反(S0C4やS0C1など)を引き起こす。ハンドラの登録と解除は、必ず「対(セット)」でコーディングする鉄則を守れ。
2. 言語間連携時の注意:
このコードはPL/Iだが、LEの基盤の上ではCOBOLやC言語ともシームレスに連携できる。もし混在環境でハンドラを入れる場合は、データ型のサイズ(`FIXED BIN(31)` や `POINTER` の長さなど)の不一致が致命傷になるので、マニュアル(IBM Language Environment プログラミング・ガイド)を座右の銘にしておくこと。
3. エラーハンドラ内での無限ループに注意:
ハンドラ自体の内部でさらに例外(例えば領域不足や未初期化ポインタの参照など)を起こすと、LEのハンドリング機構が無限ループに陥り、ジョブがフリーズするか強制的に切断される。エラーハンドラ内のコードは、極力シンプルで堅牢に書くのがプロの技だ。
—
おわりに
メインフレームのPL/I開発は、一見すると古臭く見えるかもしれない。だが、Language Environmentという強固な基盤の下で、今回紹介した `CEEHDLR` のような低水準かつ高度なシステム制御を使いこなせるようになれば、どんなにタフなバッチ要件であっても、ビクともしない堅牢なシステムを組み上げることができる。
「エラーが起きたら落ちる」のではなく、「エラーを予期し、制御し、手なずける」。
これこそが、歴戦のメインフレームアーキテクトに求められる真のスキルだ。次の改修案件では、ぜひこの動的エラーハンドリングを導入し、運用チームを唸らせてやってくれ。
