【テクニカル・上級編】CEEHDLRによるユーザー定義エラーハンドラの登録 – PL/Iの基本構文とデータ制御実践ガイド

はじめに:PL/IとLanguage Environmentが織りなす例外処理の極み

メインフレームの現場において、システム停止(アベンド)は最も忌むべき事象である。だが、どれほど周到に設計されたバッチ基幹システムであっても、想定外のデータ異常やハードウェア起因の一時障害は発生し得る。ここで問われるのが、システム全体の耐障害性(レジリエンス)だ。

IBMメインフレームの現代的な実行基盤である Language Environment (LE) では、伝統的なPL/Iの `ON` 条件ブロックを超えた、より高度で動的なエラー制御が可能である。それが今回解説する `CEEHDLR`(Condition Handler Register)を用いたユーザー定義エラーハンドラの動的登録手法だ。

JavaやC#といったモダン言語の `try-catch` 構文に慣れ親しんだエンジニアから見れば、レガシー言語における例外処理は泥臭く映るかもしれない。しかし、LEのスタックフレーム構造とPL/Iのポインタ制御を熟知したアーキテクトにとって、`CEEHDLR` は「システムが力尽きるその瞬間に、最後の意志をコードとして刻み込む」ための極めて強力な武器となる。

今回は、この `CEEHDLR` の実践的な実装パターンと、基幹システム特有の罠(パックデシマルの符号反転、埋め込みSQL/CICSのエッジケース、コンパイラ最適化の副作用)について、骨の髄まで解説しよう。

1. CEEHDLRのアーキテクチャと基本原則

`CEEHDLR` は、LEのサービスルーチンの一つであり、実行中のプログラムのスタックフレーム(アクティベーション・レコード)に対して、カスタムの例外ハンドラ(Condition Handler)を動的に登録する。

予約語を持たないPL/Iの柔軟性と落とし穴

PL/Iには、COBOLのような厳格な「予約語の壁」が極めて少ないことで知られている。識別子(変数名)の自由度が高い反面、外部ルーチンやLEサービス(`CEEHDLR` や `CEEHDLU` など)を呼び出す際には、データ型の厳密な一致が求められる。

特に `CEEHDLR` に渡すハンドラ・ルーチンは、LEの呼び出し規約(OSリンケージ)に従う必要があり、PL/Iでこれを実装するには適切なリンケージ指定とダミー引数のハンドリングが不可欠だ。

2. 実践:CEEHDLRによる動的エラーハンドラの登録と実装例

以下のPL/Iコードは、バッチ処理の中で動的にエラーハンドラを登録し、算術例外(ゼロ除算など)をトラップして、異常終了を回避(あるいは制御されたダンプ取得とクリーンアップを実施)する実用的なサンプルである。

—————————————————————-

  • モジュール名: CEEHMPL1
  • 概要: CEEHDLRを使用した動的エラーハンドラの登録と例外制御

—————————————————————-
CEEHMPL1: PROC OPTIONS(MAIN);

/ — 外部LEサービスの宣言 — /
DCL CEEHDLR ENTRY(
ENTRY, 登録するハンドラ・ルーチン
POINTER, ユーザートークン(任意データ)
FEEDBACK フィードバックコード
) OPTIONS(ASM, INTERASSEMBLER);

DCL CEEHDLU ENTRY(
ENTRY, 解除するハンドラ・ルーチン
FEEDBACK フィードバックコード
) OPTIONS(ASM, INTERASSEMBLER);

/ — データ定義 — /
DCL 1 FB_CODE, LEフィードバックコード群
3 FB_SEV FIXED BIN(15),
3 FB_MSG FIXED BIN(15),
3 FB_FLAGS BIT(32),
3 FB_I_INFO FIXED BIN(32);

DCL USER_TOKEN POINTER INIT(NULL());
DCL WORK_VAL FIXED DECIMAL(9,2) INIT(0);
DCL DIVISOR FIXED DECIMAL(9,2) INIT(0);

/ 1. エラーハンドラの動的登録 /
CALL CEEHDLR(MY_ERROR_HANDLER, USER_TOKEN, FB_CODE);
IF FB_SEV > 0 THEN DO;
PUT SKIP EDIT (‘CEEHDLR REGISTRATION FAILED.’) (A);
SIGNAL ERROR;
END;

PUT SKIP EDIT (‘— エラーハンドラ登録完了、処理を開始します —‘) (A);

/ 2. 意図的なゼロ除算の発生(テスト用) /

  • 注: 実際の基幹システムでは、外部ファイルやDB2からの不正データに相当

ON ZERODIVIDE BEGIN;
PUT SKIP EDIT (‘[PL/I ON-UNIT] ZERODIVIDE TRAPPED.’) (A);

  • ここではあえて処理を継続させず、ハンドラに委譲するケースを想定

END;

WORK_VAL = 100.00 / DIVISOR; / ここで例外発生 /

/ 3. エラーハンドラの正常解除 /
CALL CEEHDLU(MY_ERROR_HANDLER, FB_CODE);

RETURN;

—————————————————————-

  • ユーザー定義エラーハンドラ(ネイティブ・サブローチン)

—————————————————————-
MY_ERROR_HANDLER: PROC(
CERR, 条件トークン
TOKEN, ユーザートークン
RESULT, 結果(アクション)
NEW_TOKEN 新規トークン
);

DCL 1 CERR,
3 SEV FIXED BIN(15),
3 MSG_NO FIXED BIN(15),
3 FLAGS BIT(32),
3 I_INFO FIXED BIN(32);

DCL TOKEN POINTER;
DCL RESULT FIXED BIN(32);
DCL NEW_TOKEN POINTER;

/ 結果コードの定数定義 /
DCL CEE_cont FIXED BIN(32) INIT(10); 処理を継続する
DCL CEE_not_c FIXED BIN(32) INIT(20); 処理を継続しない(異常終了へ)

PUT SKIP EDIT (‘ [CEEHDLR] カスタムハンドラが例外をキャッチしました ‘) (A);
PUT SKIP EDIT (‘ メッセージ番号: ‘, MSG_NO) (A, X(1), F(5));

  • ここでログ出力や独自のクリーンアップ(ファイルのクローズ等)を実行
  • 今回は例外を処理済みにし、処理をロールフォワードせずにアベンドへ導く

RESULT = CEE_not_c;

END MY_ERROR_HANDLER;

END CEEHMPL1;

3. 基幹システムにおけるエッジケースと罠

この `CEEHDLR` やPL/Iのデータ制御をマイグレーションやバッチ改修の現場で扱う際、幾つかの「生々しい罠」に直面する。アーキテクトとして知っておくべきポイントを解説する。

① パックデシマルの内部符号反転バグと例外処理

メインフレームで多用される `FIXED DECIMAL`(パック10進数、ゾーン10進数)は、ストレージ上の最後のニブル(4ビット)に符号(`C`, `D`, `F` など)を持つ。
レガシーな電文や破損したVSAMデータセットから不正な符号が読み込まれた場合、算術演算の瞬間にハードウェア例外(Data Exception: S0C7)が発生する。

ここで注意すべきは、S0C7のようなハードウェア例外は、PL/Iの `ON CONVERSION` や `ON ERROR` で捕捉できても、CEEHDLRのレイヤに到達する前にLEのデフォルト異常処理が走る場合がある点だ。
これを防ぐには、入力データの段階で `VALID` 組み込み関数などを用いた事前検証を行うか、LEの言語環境オプションで例外の振る舞いを明示的にチューニングする必要がある。

② 埋め込みSQL(DB2)およびCICSオンライン処理との共存

CICS環境やDB2(SQL)が絡むトランザクション処理において、独自の `CEEHDLR` を仕掛ける場合、リソースの整合性に細心の注意を払わねばならない。

  • CICS環境: タスクの異常終了時にトランザクションがロールバックされる(`DFHABND` や `EXEC CICS ABEND`)が、`CEEHDLR` 内で中途半端にファイルを閉じたりすると、CICSのリカバリーマネージャとの間でリソースのデッドロックや二重解放を引き起こす。
  • DB2環境: 例外発生時に `EXEC SQL WHENEVER` やコミット/ロールバックの整合性が崩れたままハンドラが終了すると、スレッドが異常な状態でプールに戻され、CICS地域全体の障害(DFHSM0132など)に波及するリスクがある。

③ コンパイラ最適化(OPTIMIZE)の副作用

PL/Iコンパイラ(Enterprise PL/Iなど)の最適化レベルを `OPT(2)` や `OPT(3)` に引き上げると、変数のレジスタ割り付けやコードのインライン展開がアグレッシブに行われる。
この時、`CEEHDLR` に渡すポインタやトークンの参照整合性が、最適化によってコンパイラに「死んだコード(使われていない変数)」と誤認され、最適化の波に呑まれて消去されるケースがある。
動的ポインタやLEサービスを呼び出すルーチン周辺の変数には、必ず `VOLATILE` 属性を付与し、コンパイラの最適化によるレジスタキャッシュを抑制する防衛的コーディングが、シニア・アーキテクトの腕の見せ所となる。

4. マイグレーション(Java/C#等への移行)への布石

レガシーなPL/I資産をJava(Spring Frameworkなど)やC#にマイグレーションする際、この `CEEHDLR` やスタックフレーム単位の例外制御は、そのままの形では移行できない。

モダン言語における移行設計の要諦は以下の通りである。
1. 例外ハンドリングの抽象化:
PL/Iの `CEEHDLR` が担っていた「システム共通の異常時クリーンアップとログ出力」の役割を、Javaであれば `AOP (Aspect-Oriented Programming)` や、グローバルな例外ハンドラ(`ControllerAdvice` やバッチ基幹の `ItemProcessListener`)にマッピングする。
2. パックデシマルのエミュレーション:
Javaの `BigDecimal` を用いる際も、レガシー固有の符号ビット破損やアンダーフローの挙動を完全に再現するためのカスタムバリデーション層を設ける必要がある。

おわりに

PL/IとLanguage Environmentが提供する `CEEHDLR` は、単なるエラー処理の枠を超え、メインフレームのハードウェアとOSの境界線上でシステムを守り抜くための極めて洗練された仕組みである。

コードの背後にあるアーキテクチャの哲学を理解し、コンパイラの挙動からストレージの物理構造までを見通す視点こそが、私たちレガシー移行・基幹システムスペシャリストに求められている。型に嵌まった自動生成のコードではなく、こうした深遠な知見に裏打ちされた設計こそが、次世代への確かな橋渡しとなるのだ。

タイトルとURLをコピーしました