【テクニカル・上級編】CICSにおけるEXEC CICS START/RETRIEVEによる非同期処理 – PL/Iの基本構文とデータ制御実践ガイド

識別子と予約語の呪縛を抜け出し、CICS非同期の深淵へ

メインフレームの現場で長くPL/Iと向き合ってきた者なら、一度は「PL/Iには予約語が存在しない」という事実に出くわし、その自由度の高さに舌を巻いたか、あるいはその柔軟性が引き起こす悪夢のようなデバッグに頭を抱えたことがあるはずだ。

C、Java、あるいはC#の世界では、`if` や `while`、`return` といったキーワードは厳格に予約されており、変数名に流用することはできない。しかし、PL/Iの言語仕様において、これらはすべて「文脈依存のキーワード(Contextual Keywords)」に過ぎない。例えば、`IF` という名前の変数を定義することは、コンパイラの上では完全に合法である。コンパイラは、その前後の文脈を完璧に解釈し、「ここは命令文の `IF` か、それとも変数としての `IF` か」を自力で判別する。

この仕様は一見すると美しい。だが、基幹システムのモダナイゼーションや、異言語からのマイグレーション設計を行うテックリードの立場からすると、これは時限爆弾に等しい。なぜなら、人間の目には変数と予約語の区別がつきにくく、保守性の低下を招くだけでなく、CICS(Customer Information Control System)のようなリアルタイム・オンライン処理の深部において、予期せぬコンパイルエラーや最適化の罠を引き起こすトリガーになり得るからだ。

今回は、このPL/Iの特異な構文的背景を踏まえつつ、CICSオンライン環境における `EXEC CICS START` と `EXEC CICS RETRIEVE` を用いた非同期処理、そして現場でエンジニアを絶望の淵に追いやるメモリ管理やデータ構造の闇について、システムアーキテクトの視点から徹底的に紐解いていこう。

—

CICS非同期処理のメカニズム:`START` と `RETRIEVE` の裏側

基幹システムのオンライン処理において、画面応答性を担保するために重い処理を別タスクへ切り離す「非同期処理」は常套手段である。CICSではこれを `EXEC CICS START` で実現し、起動された子タスク側で `EXEC CICS RETRIEVE` を使って親タスクから渡されたデータを受け取る。

しかし、この一見シンプルに見えるデータ受け渡しの裏側では、CICSのタスク間制御ブロック(TCT:Task Control Table)や、一時的なデータ保持領域(TSキューや直接渡しのデータ)が複雑に絡み合っている。特に、ポインタとベース変数(Based Variables)を駆使して動的にメモリを切り出すPL/Iプログラムにおいて、このメカニズムを誤解していると、ストレージ・バイオレーション(ASRAアベンド)の温床となる。

1. 親タスク側:データのパッケージングと `START` 発行

親タスクでは、子タスクに渡すための構造体を組み立て、`FROM` オプションと `LENGTH` を指定して `START` を発行する。ここで重要なのは、渡すデータが「永続的なTSキュー(Temporary Storage Queue)」に書き込まれるのか、あるいはトランザクション間データ(TDキュー)なのか、それとも `START` コマンドのインラインデータとして直接渡されるのかの設計だ。

以下のPL/Iコードは、動的に割り当てたストレージを `START` コマンドで別タスクへ引き渡す実装例である。

——————————————————————

  • 親タスク:非同期トランザクション起動とデータ受け渡しサンプル

——————————————————————
ASYNC_PARENT_PROC: PROC OPTIONS(MAIN);

DCL 1 WORK_AREA,
5 REQ_CODE PIC ‘9999’,
5 ACCT_NO CHAR(10),
5 PAYLOAD_PTR POINTER;

DCL 1 CUST_DATA BASED(CUST_PTR),
5 CUST_NAME CHAR(40),
5 CUST_LIMIT FIXED DEC(11,2);

DCL CUST_PTR POINTER;
DCL DATA_LEN FIXED BIN(31,0);
DCL ERR_MSG CHAR(80);

— 動的メモリの取得(ALLOCATE文) —
ALLOCATE CUST_DATA SET(CUST_PTR);

— データの初期設定 —
Cust_Name = ‘TOKYO KIKAI KOGYO K.K.’;
Cust_Limit = 9999999.99;
Req_Code = ‘1001’;
Acct_No = ‘A123456789’;

Data_Len = LENGTH(CUST_DATA);

— CICS STARTコマンドによる非同期トランザクション起動 —
EXEC CICS START
TRANSID(‘SUB1’)
TERMID(‘DUMY’)
FROM(CUST_DATA)
LENGTH(Data_Len)
NOHANDLE;

IF EIBRESP = DFHRESP(NORMAL) THEN
DO;
正常起動時の処理
END;
ELSE
DO;
異常系ハンドリング
END;

— 領域の解放 —
FREE CUST_DATA;

RETURN;
END ASYNC_PARENT_PROC;

2. 子タスク側:`RETRIEVE` による非同期データの捕捉

`START` コマンドによって起動された子タスク側は、自身がどの親から呼ばれたのかを知るために、必ず最初の処理として `EXEC CICS RETRIEVE` を実行しなければならない。これを怠ると、データを受け取る前にタスクが終了するか、あるいは意図しないストレージ参照エラーを引き起こす。

——————————————————————

  • 子タスク:データ受信用プログラム(RETRIEVE処理)

——————————————————————
ASYNC_CHILD_PROC: PROC OPTIONS(MAIN);

DCL 1 CUST_DATA BASED(CUST_PTR),
5 CUST_NAME CHAR(40),
5 CUST_LIMIT FIXED DEC(11,2);

DCL CUST_PTR POINTER;
DCL RECV_LEN FIXED BIN(15,0);

— 領域のポインタを保持するバッファを準備 —
RECV_LEN = LENGTH(CUST_DATA);

— 親タスクから渡されたデータを取得 —
EXEC CICS RETRIEVE
INTO(CUST_DATA)
LENGTH(RECV_LEN)
NOHANDLE;

IF EIBRESP = DFHRESP(ENDFILE) THEN
— データが存在しない、または取得期限切れの場合 —
GO TO ABORT_RTN;
ELSE IF EIBRESP ^= DFHRESP(NORMAL) THEN
GO TO ABORT_RTN;

— 取得したデータを用いた業務ロジックの実行 —
CALL PROCESS_BUSINESS_LOGIC(CUST_PTR);

EXEC CICS RETURN;

ABORT_RTN:
EXEC CICS ABEND ABCODE(‘ERTR’);

RETURN;
END ASYNC_CHILD_PROC;

ここでプログラマーが陥りがちな罠が、`LENGTH` オプションの扱いだ。`RETRIEVE` コマンドで指定する `LENGTH` は、受け取る側の最大許容長を示すものであると同時に、処理完了時には実際に転送されたデータの長さに書き換えられることがある(CICSのコンパイラオプションやバージョンによる挙動の違いに注意が必要)。ここを疎かにすると、ストレージオーバーレイ事故の温床となる。

—

現場の地雷:パックデシマルの符号反転バグとポインタの闇

基幹システム移行において、最も頭を悩ませる問題の一つが、データ定義の微小な差異に起因する「パックデシマル(`FIXED DECIMAL`)の内部符号反転バグ」だ。

PL/Iでは、数値演算の精度を保つために `FIXED DEC` が多用されるが、CICSのトランザクション間通信や、DB2の埋め込みSQL(宿敵:`EXEC SQL`)との間でデータをやり取りする際、符号ニブル(ゾーン部の下位4ビット)が文字コード変換や領域の切り貼りで破損することがある。
例えば、EBCDIC環境で正数であるべき領域の符号が `C` から `F`(ゾーン)や `D`(マイナス)に化けた瞬間、演算時のデータ例外(S0C7アベンド)が直撃する。

さらに、前述した `BASED` 変数と `POINTER` を用いた動的メモリ操作では、以下のリスクが常に付きまとう。

1. ポインタのダングリング(Dangling Pointer): `FREE` した後のポインタ変数をそのまま参照し続け、別のタスクや領域を破壊する。
2. ストレージ・リーク(Storage Leak): オンラン環境で `ALLOCATE` したまま `EXEC CICS RETURN` を忘れると、タスク終了時にCICSが回収しきれない場合があります(通常はタスク終了時に解放されるが、長期稼働領域やSHARED POOLを使用している場合は致命傷になる)。
3. コンパイラ最適化の副作用: `OPTIMIZE(2)` 以上の最適化レベルを指定した際、ポインタ経由で書き換えたメモリ領域の変更が、レジスタキャッシュとの同期ズレを起こし、古い値を参照し続けるという悪夢のようなバグに遭遇することがある。これを防ぐためには、該当するポインタ変数やベース変数に対して `REORDER` または `NOREORDER` のコンパイラオプションを適切に使い分ける必要がある。

—

マイグレーション(レガシー移行)におけるアーキテクチャ設計の要諦

もし、上記のPL/IおよびCICSで構築された非同期処理システムを、Java(Spring Boot)やC#(.NET Core)へとモダナイゼーションする場合、単なる構文の置き換え(トランスレーション)だけでは100%失敗する。

  • トランザクション境界の再定義: CICSの `START` による非同期処理は、メッセージングキュー(IBM MQやRabbitMQ等)を用いた非同期メッセージング・パターンへと完全にリファクタリングする必要がある。
  • データ型の厳密な型安全性: PL/Iの自由度の高い構造体やパックデシマルは、移行先言語では `BigDecimal` や厳密なバイト配列操作(`ByteBuffer` 等)にマッピングし直す必要がある。特に、予約語を持たないPL/I特有の可読性の低いコードは、リバースエンジニアリングの段階で必ず詳細なデータフロー図に起こさなければならない。

システムアーキテクトとして、我々は単に「動くコード」を作るのではなく、レガシーが何十年かけて培ってきた「堅牢性と例外耐性」を、モダンなアーキテクチャへいかに美しく継承させるかという責務を負っている。PL/Iの泥臭い仕様の裏にある思想を理解し尽くしてこそ、真に信頼性の高いマイグレーション設計が可能となるのだ。

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