この章では,分散ロック・マネージャ (DLM) を使用して,クラスタ内の共用リソースへのアクセスの同期をとる方法について説明します。クラスタ・ファイル・システム (CFS) は,POSIX に完全に準拠しているので,アプリケーションでは
flock
システム・コールを使って,複数のインスタンスによる共用ファイルへのアクセスの同期をとることができます。また,アプリケーションではこの目的に,クラスタ DLM が提供する関数を使うこともできます。
DLM には,これまでの POSIX のファイル・ロックにはない機能があります。次のような機能です。
排他ロックや共用ロック以外の豊富なロック・モードのセット。
他のプロセスへのロック許可が阻止 (ブロック) されていることの,ロックを取得しているプロセスへの非同期通知とコールバック。
ロック要求が許可されたことの,ロックの許可を待つプロセスへの非同期通知とコールバック。
既存のロックを上位あるいは下位のロック・モードに変換する要求を出す仕組みと変換を待つ仕組み。
ロック用の独立したネームスペース。これらのネームスペースへのアクセスのさまざまな保護メカニズム。
ロック構造体内の値ブロック。リソースを共用しているプロセスは,これを使って互いの動作を伝達し調整することができる。
高度なデッドロック検出メカニズム。
注意
DLM のアプリケーション・プログラミング・インタフェース (API) ライブラリは,TruCluster Server のクラスタ・ソフトウェアとして提供され,TruCluster Server のベース・オペレーティング・システムには添付されていません。このため,スタンドアロン・システムとクラスタの両方で動作することを意図し,DLM 関数を呼び出すコードでは,
clu_is_member
を使ってアプリケーションがクラスタ内で動作しているかどうかを調べる必要があります。
この章では,基本的なDLM 操作の例についても記載しています。/usr/examples/cluster/
ディレクトリにこれらのコーディング例があります。
この章では,次の項目について説明しています。
特定のリソースに複数のプロセスがアクセスした場合,DLM はどのようにして同期をとるか (9.1 節)。
リソース,リソースの細分性 (granularity),ネームスペース,リソース名,およびロック・グループについての概念 (9.2 節)。
ロック,ロック・モード,ロックの共存性,ロック管理のキュー,ロック変換,およびデッドロックの検出の概念 (9.3 節)。
ロック要求をキューから外す
dlm_unlock
関数の使用方法 (9.4 節)。
ロック変換要求を取り消す
dlm_cancel
関数の使用方法 (9.5 節)。
ロック要求の完了通知,ロック要求の高速処理,ブロック通知,ロック変換,親ロックとサブロック,およびロック値ブロックといった高度なロック技術 (9.6 節)。
ローカル・バッファ・キャッシングをアプリケーションで行う方法 (9.7 節)。
DLM の基本的な操作を示すコーディング例 (9.8 節)。
分散ロック・マネージャ (DLM) が提供する関数を使うと,1 つのクラスタ内で連携動作するプロセスによる,raw ディスク・デバイス,ファイル,プログラムなどの共用リソースへのアクセスの同期をとることができます。DLM で,共用リソースへのアクセスの同期を効果的にとるためには,クラスタ内のプロセスが特定のリソースを共用する場合は必ず,DLM の関数を使ってリソースへのアクセスを制御しなければなりません。
DLM の関数では,次のことができます。
リソースに対するロックを要求する。
ロック,あるいはロック・グループを解放する。
既存のロックのモードを変換する。
ロック変換要求を取り消す。
ロック要求が許可されるのを待つ,または処理を続行して,要求の完了通知を非同期に受け取る。
許可されたロックによって別のロック要求がブロックされる場合に,このことを非同期に通知してもらう。
表 9-1
は,DLM が提供する関数を表にしたものです。
アプリケーションでは,libdlm
ライブラリからこれらの関数を利用することができます。
表 9-1: 分散ロック・マネージャの関数
関数 | 説明 |
dlm_cancel |
ロック変換要求を取り消す。 |
dlm_cvt |
現在のロックを,同期をとって新しいモードに変換する。 |
dlm_detach |
すべてのネームスペースからプロセスをデタッチする。 |
dlm_get_lkinfo |
指定したプロセスに関連するロック要求の情報を取得する。 |
dlm_get_rsbinfo |
DLM が管理しているリソースのロック情報を取得する。 |
dlm_glc_attach |
既存のプロセス・ロック・グループ・コンテナにプロセスをアタッチする。 |
dlm_glc_create |
グループ・ロックのコンテナを作成する。 |
dlm_glc_destroy |
グループ・ロックのコンテナを削除する。 |
dlm_glc_detach |
プロセス・ロック・グループからデタッチする。 |
dlm_lock |
指定したリソースのロックを要求する (同期式)。 |
dlm_locktp |
グループ・ロックまたはトランザクション ID を使って,指定したリソースのロックを要求する (同期式)。 |
dlm_notify |
処理待ちの完了通知やブロック通知の配信を要求する。 |
dlm_nsjoin |
指定したネームスペースにプロセスを接続する。 |
dlm_nsleave |
指定したネームスペースからプロセスを切断する。 |
dlm_perrno |
指定された DLM メッセージ ID に対応するメッセージを出力する。 |
dlm_perror |
指定された DLM メッセージ ID に対応するメッセージと,呼び出し側で指定したメッセージ文字列を出力する。 |
dlm_quecvt |
既存のロックを,非同期に新しいモードに変換する。 |
dlm_quelock |
指定したリソースへのロックを要求する (非同期)。 |
dlm_quelocktp |
グループ・ロックまたはトランザクション ID を使って,指定したリソースのロックを要求する (非同期)。 |
dlm_rd_attach |
プロセスまたはプロセス・ロック・グループを回復ドメインにアタッチする。 |
dlm_rd_collect |
指定した回復ドメインのリソースのロックで,不正なロック値ブロックを持つものを収集し,そのドメインの回復手順を開始する。 |
dlm_rd_detach |
プロセスまたはプロセス・ロック・グループを回復ドメインからデタッチする。 |
dlm_rd_validate |
指定した回復ドメインで収集したリソースを有効にし,回復手順を終了する。 |
dlm_set_signal |
完了通知およびブロック通知で使用するシグナルを指定する。 |
dlm_sperrno |
指定された DLM メッセージ ID に対応する文字列を取得し,変数に格納する。 |
dlm_unlock |
ロックを解放する。 |
DLM 自体は,リソースへのアクセスが正しく行われているかどうかを保証しません。リソースにアクセスしているプロセス間の合意によって,リソースへのアクセスが許可されます。この動作は,ロック・マネージャの使用規則に従って,DLM の関数を使用することで実現できます。この規則は次のようになっています。
すべてのプロセスが常に同じ名前でリソースを参照しなければならない。この名前は,1 つのネームスペース内で一意でなければならない。
ユーザ ID とグループ ID で修飾されたネームスペースでは,ネームスペース内で使われる保護および所有権 (つまり,ユーザ ID とグループ ID) は,クラスタ全体で矛盾しないようにしなければならない。パブリック・ネームスペースには,このようなアクセス制限はない。詳細は,9.2.2 項を参照。
すべてのプロセスは,リソースにアクセスする前にロック要求をキューに入れ,そのリソースのロックを取得しなければならない。これには,dlm_lock
,dlm_locktp
,dlm_quelock
,および
dlm_quelocktp
関数を使用する。
ロックはプロセスが所有するので,DLM を使用するアプリケーションでは次の点を考慮しなければなりません。
DLM は,シグナル,完了通知,およびブロック通知をプロセスに送信するので,スレッドを使用しているアプリケーションでは DLM の API 関数を使用しないでください。ほとんどのシグナルは,マルチスレッド・プロセス内の任意のスレッドに配信されます。(dlm_set_signal
を使用して) DLM 通知シグナルとして定義されているシグナルはすべて,ロック許可またはブロック通知を待機していないスレッドに送信されます。このため,待機中のスレッドには送信されません。
プロセスが子プロセスを生成 (fork) した場合,子プロセスは親プロセスが所有しているロックまたはアタッチしているネームスペースを受け継ぎません。子プロセスは,共用リソースにアクセスする前に,必要なリソースがあるネームスペースにアタッチし,必要なロックを取得しなければなりません。
DLM は,ブロック・ルーチンおよび完了ルーチンのプロセス空間アドレスといった,プロセス固有の情報を保持しているため,プロセスが
exec
ルーチンを呼び出して,この情報が無効になると,予期しない動作が発生します。プロセスは
exec
ルーチンを呼び出す前に,dlm_unlock
および
dlm_detach
関数を使用して,ロックを解放しなければなりません。プロセスがこれらの関数を呼び出していない場合は,DLM が
exec
ルーチンの呼び出しを失敗に終わらせます。
クラスタ内のすべてのものが,リソースとなり得ます。たとえば,ファイル,データ構造,raw ディスク・デバイス,データベース,実行可能プログラムなどがリソースになります。複数のプロセスが同じリソースに同時にアクセスする場合,これらのアクセスの同期をとらなければ,正しい結果が得られません。
ロック管理の関数を使うと,プロセスは,名前あるいはバイナリ・データとリソースを関連付けて,そのリソースへのアクセスの同期をとることができます。同期をとらなければ,あるプロセスが新しいデータを書き込んでいる間に,別のプロセスがそのリソースを読み取った場合,読み取ったデータが,書き込みによってまったく違ったものになっている可能性があります。
DLM 側から見るとリソースは,プロセス (あるいは DLM のプロセス・グループの代表プロセス) から,そのリソースの名前に対して最初にロックが要求されたときに作成されます。このとき DLM は,リソースのロック・キューやロック値ブロックなどを含む,構造体を作成します。
リソースをロックしているプロセスが 1 つでもある間は,そのリソースは存在し続けます。リソースへの最後のロックが解除されると,DLM はリソースを削除することができます。通常は,dlm_unlock
関数を呼び出すことでロックを解除しますが,プロセスが突然終了したときには,通常の手続きによらずに,ロック,そしてリソースが解放されることがあります。
9.2.1 リソースの細分性
多くのリソースはさらに細かい部品に分割することができますリソースの部品は,リソース名で識別できる限りロックすることができます。
図 9-1 は,データベースのモデルです。このデータベースはボリュームに分割され,各ボリュームはさらにファイルに分割されています。ファイルはさらにレコードに分割され,レコードはさらに項目に分割されています。
図 9-1 に示したデータベースにロック要求を出すプロセスは,データベース全体をロックすることも,個々のボリューム,ファイル,レコード,あるいは項目単位でロックすることもできます。データベース全体をロックすることを,粗い細分性でのロックといいます。単体の項目をロックすることを,細かい細分性でのロックといいます。
親ロックとサブロックとは,プロセスがさまざまな細分性のレベルでロックをかけることができるように,DLM で採用している方式のことです。親ロックとサブロックについての詳細は,9.6.5 項を参照してください。
図 9-1: データベースのモデル
ネームスペースは,リソース名のコンテナと考えることができます。セキュリティ上の理由,あるいはモジュール性を持たせる目的で,関連しないアプリケーションを分離するために複数のネームスペースが存在します。
ネームスペースは,実効ユーザ ID または実効グループ ID で修飾する場合と,パブリック・ネームスペースにして,ユーザ ID およびグループ ID に関係なく,すべてのプロセスがアクセスできるようにする場合があります。
ユーザ ID ベースのネームスペースには,そのユーザ ID を持つプロセスだけがアクセスできます。グループ ID ベースのネームスペースには,そのグループのメンバだけがアクセスできます。パブリック・ネームスペースの場合は,すべてのプロセスがそのネームスペースに加わり,ロック操作を行うことができます。
ユーザ ID またはグループID で修飾されたネームスペースの場合,セキュリティは,このネームスペースへのアクセス権を,プロセスが持つ実効ユーザ ID または実効グループ ID に基づいて制限することで保護されます。したがって,ユーザ ID およびグループ ID のネームスペースは,クラスタ全体で一貫性がなければなりません。そのネームスペースへのアクセスがプロセスに許可されれば,このネームスペース内では,個々のロック・オペレーションは制限されません。
アプリケーションが,パブリック・ネームスペースを使用する場合は,セキュリティやアクセス・メカニズムは,アプリケーション側で用意しなければなりません。パブリック・ネームスペース内でロックへのアクセスを制御し保護する方法としては,ロックを保持しているプロセスが,保護読み取り (PR) モード以上のモードを維持し,すべてのブロック通知に応答しないようにするものがあります。こうすると,ロックを保持しているプロセスが実行している間は,他のプロセスがロック値ブロックを変更することはできません。
連携して動作するプロセスは,特定のリソースへのロックを調整できるように,同じネームスペースを使用しなければなりません。プロセスが,dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,または
dlm_quelocktp
関数を呼び出してリソースのロックを取得するためには,必要なリソースがあるネームスペースに加わらなければなりません。プロセスが,ユーザ ID またはグループ ID で修飾されたネームスペースに対して
dlm_nsjoin
関数を呼び出すと,DLM はそのプロセスのグループ ID またはユーザ ID によって,そのプロセスがそのネームスペースへのアクセスを許可されていることを確かめます。プロセスがこのチェックにパスすると,DLM は,ネームスペースへのハンドルを返します。プロセスが,パブリック・ネームスペースに対して
dlm_nsjoin
関数を呼び出した場合は,DLM はすぐにそのネームスペースへのハンドルを返します。
プロセスは,次に DLM 関数を呼び出してルート・ロックを取得するときに,このハンドルを指定しなければなりません。ルート・ロックとは,ネームスペース内の特定のリソースをロックするための,元となる親ロックのことです。ルート・ロックの下位には,ネームスペースへのアクセス権のチェックを受けることなく,サブロックを追加することができます。
プロセスは,最大
DLM_NSPROCMAX
個のネームスペースのメンバになることができます。
9.2.3 リソースの識別
DLM は以下のパラメータで,リソースを識別します。
ネームスペース (nsp
)
--
dlm_nsjoin
関数を使ってネームスペース・ハンドルを取得した後に,dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,または
dlm_quelocktp
関数を呼び出して,ネームスペースのトップ・レベル (ルート) のロックを取得します。ルート・ロックには親がありません。
プロセスが指定するリソース名 (resnam
)
-- プロセスが指定する名前は,そのプロセスによってロックされるリソースを示します。他のプロセスで,そのリソースにアクセスする必要がある場合は,同じ名前を使ってそのリソースを参照しなければなりません。名前とリソースの相互関係は,連携動作するプロセス間で同意された規則で決まります。
リソース名の長さ (resnlen
)
要求時に指定した親ロックの識別子 (parid
)
-- 親ロック ID に 0 を指定するロック要求がキューに入れられた場合,ロック・マネージャはこれを,そのリソースへのルート・ロックの要求と見なします。ロック要求で,親ロック ID に 0 以外が指定された場合は,そのリソースへのサブロックの要求と見なします。この場合,DLM はルート・ロックが許可されている場合のみ,この要求を受け付けます。この方式では,プロセスは複数のレベルの細分性でリソースをロックすることができ,これによってロック・ツリーが構築されます。
たとえば,次の 2 つのパラメータ・セットは同じリソースを表します。
パラメータ | nsp | resnam | resnlen | parid |
リソース 1 | 14 | disk1 | 5 | 80 |
リソース 1 | 14 | disk1 | 5 | 80 |
パラメータ | nsp | resnam | resnlen | parid |
リソース 1 | 14 | disk1 | 5 | 40 |
リソース 1 | 14 | disk12345 | 5 | 40 |
次の 2 つのパラメータ・セットは異なるリソースを表します。
パラメータ | nsp | resnam | resnlen | parid |
リソース 1 | 0 | disk1 | 5 | 80 |
リソース 2 | 0 | disk1 | 5 | 40 |
分散ロック・マネージャ (DLM) の関数を使うには,プロセスは
dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,または
dlm_quelocktp
関数を使って,リソースへのアクセス (つまりロック) を要求しなければなりません。この要求では,以下のようなパラメータを指定します。
あらかじめ
dlm_nsjoin
関数を呼び出して取得したネームスペース・ハンドル -- 実効ユーザ ID またはグループ ID で修飾されたネームスペースの場合,DLM はまず,そのプロセスがそのネームスペースへのアクセス権を持っていることを検証してから,そのネームスペース内のリソースへのロックを与え,ロックを操作できるようにします。パブリック・ネームスペースは,すべてのプロセスがアクセスすることができます。ネームスペースについての詳細は,9.2.2 項を参照してください。
リソースを表すリソース名 -- リソース名の意味は,アプリケーション・プログラム側で定義します。DLM は,このリソース名を,複数のプロセスからのロック要求を対応付けるためのメカニズムとして利用します。リソース名はネームスペース内に存在します。同じ名前のリソースが異なるネームスペースにあった場合,DLM ではこれらを異なるリソース名と見なします。
リソース名の長さ -- リソース名の長さは,1 〜
DLM_RESNAMELEN
バイトです。
親ロック ID -- 親 ID には,ルート・ロックを要求する場合は 0 を指定し,その親のサブロックを要求する場合は,0 以外の値の親 ID を指定します。詳細は,9.2.2 項を参照してください。
DLM がロック ID を返すメモリ・アドレス --
dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,および
dlm_quelocktp
関数は,要求が許可されると,ロック ID を返します。アプリケーションでは,これ以降の操作で,つまり
dlm_cvt
関数,dlm_quecvt
関数,および
dlm_unlock
関数などの呼び出しで,このロックを参照するために,このロック ID を使います。
ロックの要求モード -- DLM の関数は,新規に要求されたロックのモードと,これと同じリソース名を持つ他のロックのモードを比較します。ロック・モードについての詳細は,9.3.1 項を参照してください。
ヌル・モードのロック (9.3.1 項を参照) は,他のすべてのロック・モードと共存でき,いつでもすぐに許可されます。
次の場合は,新規のロックはすぐに許可されます。
該当するリソースを他のプロセスがロックしていない場合。
他のプロセスが該当するリソースをロックしていて,新しく要求したロックのモードが現在のロックと共存でき,なおかつ,変換待ちキュー,または待ちキューで待たされているロックがない場合。ロック・モードの共存性についての詳細は,9.3.2 項を参照してください。
他のプロセスが既にそのリソースをロックしていて,新規要求のモードが現在のロック・モードと共存できない場合。新規の要求は,先入れ先出し (FIFO) のキューに置かれます。新規のロック要求は,リソースに現在許可されているロック・モード (リソースのグループ許可モード) が,新規のロック要求と共存できる状態になるまでここで待たされます。
プロセスは,dlm_cvt
関数および
dlm_quecvt
関数を使ってロック・モードを変更することもできます。これをロック変換と呼びます。詳細は,9.3.4 項を参照してください。
9.3.1 ロック・モード
他のロック要求とリソースを共用できるかどうかは,ロックのモードで決まります。表 9-2
では,6 つのロック・モードについて説明します。
表 9-2: ロック・モード
モード | 説明 |
ヌル (DLM_NLMODE ) |
リソースへのアクセスは許可しない。ヌル・モードは,後でロック変換を行うための準備として使用する。またはリソースのロックがすべて解放されても,そのリソースとリソースのコンテキストが保持されるようにするために使用する。 |
同時読み取り (DLM_CRMODE ) |
リソースへの読み取りアクセスを許可し,他の読み取りや書き込みプロセスとのリソースの共用を許す。引き続き,より細かい細分性でサブロックを実行する場合や,保護していない状態で,つまり書き込みを許したままリソースからデータを読み取る場合には,通常,同時読み取りモードが使われる。 |
同時書き込み (DLM_CWMODE ) |
リソースへの書き込みアクセスを許可し,他の書き込みプロセスとのリソースの共用を許す。引き続き,より細かい細分性でロックを実行する場合や,保護していない状態でリソースに書き込む場合には,通常,同時書き込みモードが使われる。 |
保護読み取り (DLM_PRMODE ) |
リソースへの読み取りアクセスを許可し,他の読み取りプロセスとのリソースの共用を許す。リソースへの書き込みアクセスは許可しない。一般的にいう,共用ロック。 |
保護書き込み (DLM_PWMODE ) |
リソースへの書き込みアクセスを許可し,同時読み取りモードの読み取りプロセスとのリソースの共用を許す。他の書き込みプロセスによる書き込みアクセスは許さない。一般的にいう,更新ロック。 |
排他 (DLM_EXMODE ) |
リソースへの書き込みアクセスを許可し,他の読み取りプロセスや書き込みプロセスとのリソースの共用は許さない。一般的にいう,排他ロック。 |
複数のプロセスでリソースを共用できるロックを低レベルのロックと呼び,プロセスがほぼ排他的にリソースにアクセスできるロックを高レベルのロックと呼びます。ヌル・モードおよび同時読み取りモードのロックは低レベルのロックです。保護書き込みモードおよび排他モードのロックは高レベルのロックです。アクセス・モードをレベルの低い順に並べると,次のようになります。
ヌル (NL)
同時読み取り (CR)
同時書き込み (CW) および 保護読み取り (PR)
保護書き込み (PW)
排他 (EX)
同時書き込み (CW) と保護読み取り (PR) モードは,同じレベルと見なされます。
リソースに対して許可されている他のロックと共用できるロック (つまり,リソースのグループ許可モード) は,共存できるロック・モードを持っているといわれます。高レベルのロック・モードは,低レベルのロック・モードに比べ,他のロック・モードとの共存性が低くなります。
表 9-3
では,ロック・モードの共存性を示します。
表 9-3: ロック・モードの共存性
要求されたロックのモード | リソースのグループ許可モード | |||||
NL | CR | CW | PR | PW | EX | |
ヌル (NL) | 可能 | 可能 | 可能 | 可能 | 可能 | 可能 |
同時読み取り (CR) | 可能 | 可能 | 可能 | 可能 | 可能 | 不可能 |
同時書き込み (CW) | 可能 | 可能 | 可能 | 不可能 | 不可能 | 不可能 |
保護読み取り (PR) | 可能 | 可能 | 不可能 | 可能 | 不可能 | 不可能 |
保護書き込み (PW) | 可能 | 可能 | 不可能 | 不可能 | 不可能 | 不可能 |
排他 (EX) | 可能 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 |
リソースに対するロックは,次の 3 種類の状態のいずれかになります。
許可 -- ロック要求が許可された状態。
変換待ち -- あるモードでロックが許可され,リソースの現在のグループ許可モードと共存できるモードへの変換要求が,許可されるのを待っている状態。
待ち -- 新規のロック要求が許可されるのを待っている状態。
図 9-2
で示すように,3 種類のそれぞれの状態に対応したキューがあります。
図 9-2: 3 つのロック・キュー
既存のリソースに対して,新規にロックを要求すると,DLM は次のようにして,変換待ちキューまたは待ちキューで待っているロックが他にあるかどうかを確認します。
いずれかのキューで他のロックが待たされている場合,新規のロック要求は待ちキューの最後に置かれます。ただし,ヌル・モードのロックを要求した場合は,キューには置かれず,その要求はすぐに許可されます。
変換待ちキューと待ちキューが両方とも空の場合,ロック・マネージャは新規のロックが,他に許可されているロックと共存できるかどうかを確認します。共存できる場合は,ロックが許可されます。共存できない場合は,待ちキューに置かれます。dlm_lock
,dlm_locktp
,dlm_quelock
,dlm_quelocktp
,dlm_cvt
,dlm_quecvt
のいずれかを呼び出す際に,DLM_NOQUEUE
フラグを指定すると,すぐに許可されない場合はロック要求をキューにいれないように,DLM に指示することができます。この場合,リソースのグループ許可モードと共存できる場合は,ロック要求が許可され,共存できない場合は,DLM_NOTQUEUED
エラーで拒否されます。
ロック変換を行うことにより,プロセスはロックのモードを変更することができます。たとえば,プロセスはリソースに対して低レベルのロックを保持しておいて,リソースへのアクセスを制限しようと決めた段階で,ロック変換を行うことができます。
ロック変換を行うには,変換したい,許可されているロックのロック ID を指定して,dlm_cvt
関数,または
dlm_quecvt
関数を呼び出します。要求したロック・モードが現在許可されているロックと共存できる場合,変換要求はすぐに許可されます。要求したロック・モードが,許可キューにある既存のロックと共存できない場合,要求は変換待ちキューの最後に置かれます。変換要求が許可されるまでは,ロックは現在許可されているモードのままです。
DLM は,ある変換要求を許可すると,変換待ちキューに並んでいる順に共存可能な要求を許可していきます。DLM は,変換待ちキューが空になるまで,または共存できないロックに出会うまで,順番に要求を許可していきます。
変換待ちキューが空の場合,DLM は待ちキューを調べます。待ちキューの先頭のロック要求が,現在許可されているロックと共存できれば,要求が許可されます。DLM は,待ちキューが空になるまで,または共存できないロックに出会うまで,順番に要求を許可していきます。
9.3.5 デッドロックの検出
DLM は次の 2 種類の形態のデッドロックを検出することができます。
変換デッドロック -- 変換デッドロックは,変換要求が持っている許可モードが,変換待ちキューの 1 つ前の変換要求で要求されたモードと共存できない場合に起こります。これを,図 9-3
の例を使って説明します。1 つのリソースに対して,PR モードのロックが 2 つ許可されています。つまり,そのリソースの許可モードは PR です。PR モードのロックの 1 つがモードを EX モードに変換しようとし,その結果変換キューで待つことなりました。次に,2 つ目の PR モードのロックも同様に,モードを EX に変換しようとすると,これは,変換待ちキューの 1 つ目のロック要求の後で待たなければなければなりません。しかし,1 つ目のロックが要求するモード (EX) は,2 つ目のロックに現在許可されている PR モードとは共存できません。このため,1 つ目のロック要求が許可されることはありません。2 つ目のロック要求は,変換待ちキューの中で,1 つ目のロック要求の後ろで待っているため,この要求が許可されることもありません。
図 9-3: 変換デッドロック
複数リソース・デッドロック -- 複数リソース・デッドロックは,一連のプロセスが互いに待ち合い,待ち循環に陥った場合に起こります。これを,図 9-4 の例を使って説明します。3 つのプロセスの要求が,それぞれのリソースのキューに入っています。それぞれの現在のロックが解放されるまで,またはより低いモードに変換されるまで,これらのリソースにはアクセスできません。それぞれのプロセスが,別のプロセスがロック要求を解放するのを互いに待っています。
DLM は,変換デッドロック,または複数リソース・デッドロックが発生したことを検出すると,犠牲にするロックを選び,これを使ってデッドロックを解消します。犠牲にするロックは任意に選択されますが,選ばれたロックは,変換待ちキュー,または待ちキューのどちらかに置かれることが保証されます (つまり,許可キューには置かれません)。
DLM は,dlm_lock
関数,dlm_locktp
関数,dlm_cvt
関数のいずれかを呼び出し,犠牲にされたプロセスに,最終的な終了状態コードとして
DLM_DEADLOCK
を返します。あるいは,dlm_quelock
関数,dlm_quelocktp
関数,dlm_quecvt
関数のいずれかを呼び出す際に指定された完了ルーチンへのパラメータ
completion_status
として,この状態を渡します。許可されたロックが取り消されることはありません。変換待ちキューおよび待ちキューのロック要求だけが,DLM_DEADLOCK
状態コードを受け取る可能性があります。
注意
DLM がデッドロックを解消する際に,どのロックを選択するかを想定しないでください。また,セマフォやファイル・ロックのような他のサービスを DLM と一緒に使用した場合は,検出できないデッドロックが発生する可能性があります。DLM で検出できるデッドロックは,DLM のロックに関連して発生したデッドロックのみです。
プロセスは,リソースをロックする必要がなくなった場合,dlm_unlock
関数を呼び出して,ロックを解放することができます。
ロックが解放されると,そのロック要求は,どのキューにあっても削除されます。ロックは,許可キュー,待ちキュー,変換待ちキューのいずれのキューからも外されます。あるリソースの最後のロックがキューから外されると,そのリソースは DLM のデータベースから削除されます。
dlm_unlock
関数では,valb_p
パラメータと
DLM_VALB
フラグを指定することで,リソースのロック値ブロックへ書き込んだり,内容を無効にすることができます。キューから外されるロックに PW モードまたは EX モードが許可されている場合,プロセスの値ブロックの内容が,リソースの値ブロックに格納されます。キューから外されるロックが上記以外のモードの場合,ロック値ブロックは使用されません。DLM_INVVALBLK
フラグを指定した場合は,リソースのロック値ブロックに,無効であることを示す印が付けられます。
dlm_unlock
関数では以下のフラグを使用できます。
DLM_DEQALL
フラグは,表 9-4
で示す
lkid_p
パラメータの値に従って,プロセスが保持するすべてのロックをキューから外すか,あるいはロックのサブ・ツリーをキューから外すことを指示します。
表 9-4: dlm_unlock 関数の呼び出しでの DLM_DEQALL フラグの使用
lkid_p | DLM_DEQALL | 結果 |
<> 0 | 設定しない | lkid_p で指定されたロックのみが解放される。 |
<> 0 | 設定 | 指定されたロックのすべてのサブロックが解放される。lkid_p で指定されたロックは解放されない。 |
= 0 | 設定しない | 無効ロック ID の条件値 (DLM_IVLOCKID ) が返される。 |
= 0 | 設定 | プロセスが保持しているすべてのロックが解放される。 |
DLM_INVVALBLK
フラグを指定すると,DLM は,許可キューまたは変換待ちキューにある PW モードまたは EX モードのロックの,リソースのロック値ブロックを無効にします。
リソースのロック値ブロックは,再度書き込まれるまで,無効の印が付いたままになります。ロック値ブロックについての詳細は,9.6.6 項を参照してください。
DLM_VALB
フラグを指定すると,DLM は,許可キューまたは変換待ちキューにある PW モードまたは EX モードのロックについて,リソースのロック値ブロックへ書き込みます。
DLM_VALB
フラグと
DLM_INVVALBLK
フラグの両方を,同じ要求で指定することはできません。
9.5 変換要求の取り消し
dlm_cancel
は,ロック変換を取り消す関数です。プロセスは,ロック要求がまだ許可されていない場合だけ,つまり,ロック要求が変換待ちキューにある場合だけ,ロック変換を取り消すことができます。取り消しを実行すると,変換待ちキュー内のロックは,変換要求を出す前に許可されていたモードに戻ります。ロックの値
blkrtn
および
notprm
も元の値に戻ります。DLM は,変換要求で指定されている完了ルーチンを呼び出して,要求が取り消されたことを伝えます。返される状態コードは,DLM_CANCEL
です。
9.6 高度なロック技術
ここまでの節では,すべてのアプリケーションに役立つロック技術および概念について説明してきました。これ以降の各項では,分散ロック・マネージャ (DLM) のより特化された機能について説明します。
9.6.1 ロック要求の非同期完了
dlm_lock
関数,dlm_locktp
関数,および
dlm_cvt
関数は,ロック要求が許可されるか失敗したときに完了し,結果を示す状態コードが返されます。
ロック要求が完了するまでアプリケーションを待たせたくない場合は,dlm_quelock
関数,dlm_quelocktp
関数,および
dlm_quecvt
関数を使うようにしてください。これらの関数は,ロック要求をキューに入れた後に,呼び出し元のプログラムに制御を返します。これらの関数が返す状態値は,要求が正常にキューに入れられたか,または拒否されたかを知らせるものです。要求がキューに入れられた後,呼び出し元のプログラムは,要求が許可されるまではリソースにアクセスすることはできません。
dlm_quelock
関数,dlm_quelocktp
関数,および
dlm_quecvt
関数の呼び出しでは,完了ルーチンのアドレスを指定しなければなりません。ロック要求に成功した場合,または失敗した場合に,完了ルーチンが実行されます。DLM は,ロック要求が成功したか,または失敗したかを示す状態情報を完了ルーチンに渡します。
注意
DLM からの完了通知を受けたいアプリケーションでは,この通知が必要な最初のロック要求を出す前に一度,
dlm_set_signal
関数を呼び出さなければなりません。これ以外に,アプリケーションがdlm_notify
関数を定期的に呼び出す方法もあります。dlm_notify
関数を使うと,プロセスは,保留になっている通知をポーリングし,dlm_set_signal
関数を呼び出さなくても,完了通知の配信を要求することができます。ただし,このポーリングによる方法は,お勧めしません。
DLM には,ロック要求がその場で許可されたかどうかをプロセスが確認する機能があります。つまり,そのロックが変換待ちキュー,または待ちキューに入れられなかったかどうかを確認できます。この機能を使うと,シグナルの配信と,その結果の完了ルーチンの実行によるオーバヘッドが回避されるため,ほとんどのロックがその場で許可される (これが一般的) アプリケーションでは,性能を改善することができます。アプリケーションでは,要求を処理する際にこの機能を使って,衝突するロックがないかテストすることもできます。
この機能のメカニズムは,次のようになっています。
dlm_lock
関数,dlm_locktp
関数,dlm_cvt
関数,dlm_quelock
関数,dlm_quelocktp
関数,dlm_quecvt
関数のいずれかを呼び出す際に,DLM_SYNCSTS
フラグを設定し,その場でロックが許可されると,これらの関数は,呼び出し元に状態値
DLM_SYNCH
を返します。dlm_quelock
関数,dlm_quelocktp
関数,および
dlm_quecvt
関数の場合,DLM は完了通知を配信しません。
dlm_quelock
関数,dlm_quelocktp
関数,および
dlm_quecvt
関数を呼び出して開始したロック要求が,その場で完了しなかった場合は,これらの関数から状態値
DLM_SUCCESS
が返されます。この値は,要求がキューに入れられたことを示します。ロックが正常に許可されるか,またはロックに失敗すると,DLM は完了通知を配信します。
DLM 関数を使用するアプリケーションの中には,他のプロセスがリソースをロックするのを防げているかどうかを,プロセスが意識しなければならないものがあります。DLM では,ブロック通知を使って,プロセスにこのことを知らせます。ブロック通知を使うには,ロック要求の blkrtn パラメータにブロック通知ルーチンのアドレスを指定しなければなりません。これにより,このロックのために,別のロックの許可が防げられたときに,ブロック通知が配信され,ブロック通知ルーチンが実行されます。
DLM は,ブロック通知ルーチンに以下のパラメータを渡します。
ブロックされたロックのコンテキスト・パラメータ。このパラメータは,ブロックされたロックのロック要求で,dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,dlm_quelocktp
関数,dlm_cvt
関数,dlm_quecvt
関数のいずれかの関数の呼び出し元プロセスが指定したものです。
最初にブロックされたロックの
hint
パラメータ。このパラメータは,最初にブロックされたロックのロック要求で,dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,dlm_quelocktp
関数,dlm_cvt
関数,dlm_quecvt
関数のいずれかの関数の呼び出し元プロセスが指定したものです。
ブロックされたロックのロック ID へのポインタ。
最初にブロックされたロックで要求されたモード。
通知が配信されるまでに,次のような状況になっている可能性があります。
ロックはブロックされたまま。
ブロックされたロックがアプリケーションによって解放され,実際にはブロックされているロックがない。
ブロックされたロックが,デッドロックを解除するための犠牲にされ,要求が失敗した。
ブロックされたロックがアプリケーションによって解放され,別のロックがキューに入れられブロックされている。したがって,まったく別のロックが実際にはブロックされている。
最初にブロックされたロックの後に他のロックがたまっている,つまりブロックされたロックがキューの後に入れられている。
こうした状況が発生する可能性があるため,DLM では,ブロック・ルーチンの実行時の,blocked_hint パラメータ,および blocked_mode パラメータの有効性を保証することはできません。
注意
DLM からブロック通知の配信を受けたいアプリケーションでは,ブロック通知を必要とする最初のロック要求を発行する前に,
dlm_set_signal
関数を (1 度) 呼び出さなければなりません。また,
dlm_set_signal
の呼び出し時に指定したシグナルがブロックされた場合,このシグナルのブロックが解除されるまで,ブロック通知は配信されません。これ以外に,dlm_notify
関数を定期的に呼び出す方法があります。dlm_notify
関数を使うと,プロセスは,保留になっている通知をポールし,これらの通知を配信するように要求することができます。ただし,このポーリングによる方法は,お勧めしません。
ロック変換には,次のような機能があります。
ロック・モードの変換 (上方向または下方向) -- DLM では,低レベルでロックし,それを必要に応じて高レベルのモードに変換することができます。通常,プログラムでは,データの書き込み中は,排他 (EX) モードまたは保護書き込み (PW) モードのロックが必要です。しかし,そのプログラムでの書き込み処理が常に必要なわけではないので,そのプログラムの開始から終了までリソースを排他的にロックしておきたくないこともあります。EX モード,または PW モードのロックがかかっていると,他のプロセスがそのリソースにアクセスできません。ロック変換機能を使うことにより,プロセスは,最初は低レベルのロックを要求し,データの書き込みが必要になったときだけロックのモードを上位レベルのロック・モード (たとえば PW モード) に変換することができます。しかし,プロセスはリソースへの書き込みを常時必要とするわけではないので,書き込みが終了したら,下位レベルのロック・モードにロックを変換してください。
注意
別のタイプのアプリケーション,たとえば,書き込みアクセスの共用がそれほど重要でないアプリケーションでも,DLM のロック変換機能を使うことができます。たとえば,アプリケーションのあるインスタンスがマスタになり,同じアプリケーションの別のインスタンスがスレーブ・インスタンス,または 2 次インスタンスになるような場合,ロックおよびロック変換を使って,マスタ・インスタンスが異常終了したときの回復手順を高速化することができます。
リソースのロック値ブロックに格納されている値の維持 -- ロック値ブロックは,同じリソースをロックしているプロセス間で共用する小さなメモリ領域です。アプリケーションによっては,ロック値ブロックを使う必要があります。バージョン番号やその他のデータをロック値ブロックに格納している場合,値ブロックが消されないように,リソースに対して少なくとも 1 つのロックをかけておく必要があります。この場合プロセスは,そのリソースへのアクセスが完了したときに,ロックをキューから外すのではなく,ヌル・ロックに変換します。ロック値ブロックの使用についての詳細は,9.6.6 項を参照してください。
一部のアプリケーションの性能改善 -- アプリケーションの中には,性能を改善するために,ロックする可能性のあるリソースすべてを,初期化時にヌル・モードでロックするものがあります。ヌル・モードのロックは,必要に応じて上位レベルのロックに変換することができます。多くの場合,変換の要求の方が,新規ロックの要求よりも高速に処理されます。これは,変換の要求では必要なデータ構造が既に構築されているためです。ただし,プログラムを実行している間,ロックをかけたままにしておくと,システムの動的メモリが消費されます。したがって,必要なロックすべてを NL モードで作成し,必要に応じてこれらを変換するテクニックを用いれば,メモリ使用量は増えますが,性能を改善することができます。
ロック変換を行う場合,dlm_cvt
関数または
dlm_quecvt
関数を呼び出します。変換するロックは,lkid_p
パラメータで指定します。ロック変換を要求するときには,そのロックは既に許可されていなければなりません。
9.6.4.2 変換待ちキューへの強制的配置
特定のリソースへのアクセスがより公平に行われるようにするために,許可できる変換要求を,強制的に変換待ちキューに入れることができます。DLM_QUECVT
フラグを設定して変換要求を行うと,その要求は,既にキューに入っている変換要求の後ろに強制的に置かれます。このように,DLM_QUECVT
フラグを指定して,他のロックが許可されるチャンスをつくることができます。ただし,キューに入っている変換要求がない場合は,その変換要求はすぐに許可されます。
DLM_QUECVT
は,可能な変換要求のサブセットに対してだけ有効です。表 9-5
は,DLM_QUECVT
フラグを指定した場合に許される,変換要求の組み合わせを示しています。無効な変換要求は,DLM_BADPARAM
状態が返されて,失敗します。
表 9-5: DLM_QUECVT フラグを指定した場合に許される変換
取得しているロックのモード | 変換後のロックのモード | |||||
NL | CR | CW | PR | PW | EX | |
ヌル (NL) | -- | 可 | 可 | 可 | 可 | 可 |
同時読み取り (CR) | -- | -- | 可 | 可 | 可 | 可 |
同時書き込み (CW) | -- | -- | -- | -- | 可 | 可 |
保護読み取り (PR) | -- | -- | -- | -- | 可 | 可 |
保護書き込み (PW) | -- | -- | -- | -- | -- | -- |
排他 (EX) | -- | -- | -- | -- | -- | -- |
プロセスが
dlm_lock
関数,dlm_locktp
関数,dlm_quelock
関数,dlm_quelocktp
関数のいずれかを呼び出してロック要求を出す際に,parid
パラメータに親ロック ID を指定すると,新規のロックに対して親ロックを宣言することができます。親ロックを持つロックを,サブロックと呼びます。親ロックが許可された後でなければ,その親に属するサブロックは許可されません。モードは,親と同じモードでも,別のモードでもかまいません。
親ロックとサブロックを使うメリットは,細かい細分性のリソースに,高レベルのサブロック (保護書き込みモード,または排他モード) をかけ,一方で粗い細分性のリソースに,低レベルのロック (同時読み取りモード,または同時書き込みモード) をかけられることです。たとえば,低レベルのロックで,あるファイル全体へのアクセスを制御し,それと同時に,それより上位のレベルで,そのファイル内の個々のレコードやデータ項目にサブロックをかけることができます。
データベースへのアクセスが必要なプロセスがたくさんある場合を考えます。データベースを,ファイルのロックとレコードのロックの 2 つのレベルでロックできるとします。ファイル内のすべてのレコードを更新する場合,ファイル全体をロックして,他のロックは行わずにレコードを更新した方が,高速で効率もよくなります。しかし,特定のレコードを更新する場合は,必要に応じてレコードをロックする方が効率的です。
親ロックをこのように使う場合,すべてのプロセスの要求で,ファイルをロックします。すべてのレコードを更新するプロセスは,該当するファイルに対して,保護書き込み (PW) モード,または排他 (EX) モードのロックを要求しなければなりません。個々のレコードを更新するプロセスは,ファイルに対して同時書き込み (CW) モードのロックを要求し,その後,個々のレコードに対して,PW モード または EX モードのサブロックをかけます。
このように,すべてのレコードにアクセスする必要があるプロセスはファイルをロックすることでアクセスでき,一方で,ファイルを共用するプロセスは,個々のレコードをロックすることができます。これにより,多くのプロセスが,ファイル・レベルのロックを同時書き込みモードで共用する一方で,サブロックを使って特定のレコードを更新することができます。
9.6.6 ロック値ブロック
ロック値ブロックは
DLM_VALBLKSIZE
のサイズの符号なしロングワードの構造体です。この構造体は,プロセスが DLM の関数を呼び出す際に
valb_p
パラメータおよび
DLM_VALB
オプションを指定することで,リソースに対応付けます。ロック・マネージャがリソースを作成する際に,このリソースのロック値ブロックも作成されます。DLM は,リソースへのロックがなくなるまで,リソースのロック値ブロックを維持します。
プロセスが新しいロックを要求するときに,DLM_VALB
オプションを指定し,valb_p
パラメータに有効なアドレスを指定して,これが許可された場合,リソースのロック値ブロックの内容は,プロセスのロック値ブロックにコピーされます。
プロセスが,PW モードまたは EX モードから,同等レベルまたは下位レベルのモードへのロック変換で,valb_p
パラメータと
DLM_VALB
オプションを指定した場合,プロセスのロック値ブロックの内容が,リソースのロック値ブロックに格納されます。
このようにしてプロセスは,リソースの所有権とともに,ロック値ブロックの値を渡し,更新することができます。表 9-6
では,ロック変換によって,プロセスおよびリソースのロック値ブロックの内容が,どのように変化するかを示します。
表 9-6: ロック変換によるロック値ブロックへの影響
取得しているロックのモード | ロック変換後のモード | |||||
NL | CR | CW | PR | PW | EX | |
ヌル (NL) | 読取り | 読取り | 読取り | 読取り | 読取り | 読取り |
同時読み取り (CR) | -- | 読取り | 読取り | 読取り | 読取り | 読取り |
同時書き込み (CW) | -- | -- | 読取り | -- | 読取り | 読取り |
保護読み取り (PR) | -- | -- | -- | 読取り | 読取り | 読取り |
保護書き込み (PW) | 書込み | 書込み | 書込み | 書込み | 書込み | 読取り |
排他 (EX) | 書込み | 書込み | 書込み | 書込み | 書込み | 書込み |
PW モードまたは EX モードで許可されているロックが
dlm_unlock
関数を使って解放されて,ロック値ブロックのアドレスが
valb_p
パラメータに指定され,DLM_VALB
オプションが指定されていると,プロセスのロック値ブロックの内容がリソースのロック値ブロックに書き込まれます。解放されるロックがこれ以外のモードの場合は,ロック値ブロックは使用されません。
リソースのロック値ブロックが無効になる場合があります。このような場合,DLM は
valb_p
パラメータを指定して関数を呼び出したプロセスに対し,DLM_SUCCVALNOTVALID
または
DLM_SYNCVALNOTVALID
の終了状態を返して警告します。次のような場合に,リソースのロック値ブロックが無効になることがあります。
PW モードまたは EX モードでリソースをロックしているプロセスが異常終了した場合。
ロックに関与しているノードが停止し,そのノード上のプロセスが PW モードまたは EX モードでリソースをロックしていた場合,あるいはロックしていた可能性がある場合。
PW モードまたは EX モードでリソースにロックをかけているプロセスが,ロックをキューから外すために
dlm_unlock
関数を呼び出し,flags
パラメータに
DLM_INVVALBLK
フラグを指定している場合。
9.7 DLM 関数を使ったローカル・バッファ・キャッシング
アプリケーションは,分散ロック・マネージャ (DLM) を使って,ローカル・バッファ・キャッシング (分散型バッファ管理とも呼ぶ) を行うことができます。ローカル・バッファ・キャッシングを行うと,多くのプロセスが,データ (たとえばディスク・ブロックなど) のコピーを,各プロセスのローカル・バッファに持つことができます。他のプロセスによる変更で,バッファ内のデータが有効でなくなった場合は,各プロセスに通知されます。変更の頻度が低いアプリケーションでは,バッファのコピーをローカルに持つことにより,入出力動作を大幅に削減することができます。このため,ローカル・バッファ・キャッシングあるいは分散型バッファ管理という名前で呼ばれています。ロック値ブロック,またはブロック通知 (あるいはその両方) を使って,バッファ・キャッシングを行うことができます。
9.7.1 ロック値ブロックを使う方法
ロック値ブロックを使ってローカル・バッファ・キャッシングを行うため,バッファをキャッシュしている各プロセスは,各バッファの現在の内容に対応するリソースに,ヌル (NL) モードのロックをかけます。この説明では,バッファにディスク・ブロックが格納されていることを前提とします。各リソースに対応するロック値ブロックは,ディスク・ブロックのバージョン番号を入れておくために使います。あるディスク・ブロックに初めてロックがかけられたとき,アプリケーションは,プロセスのロック値ブロックにある,そのディスク・ブロックの現在のバージョン番号を返します。
バッファの内容をキャッシュすると,このバージョン番号はバッファの内容とともに保存されます。バッファの内容を再利用するには,バッファを読み取るのか書き込むのかによって,NL モードのロックを,保護読み取り (PR) モードまたは排他 (EX) モードに変換しなければなりません。この変換で,ディスク・ブロックの最新のバージョン番号が返されます。アプリケーションでは,ディスク・ブロックのバージョン番号と,保存していたバージョン番号を比較します。これらが等しい場合,キャッシュされているコピーは有効です。これらが等しくない場合には,アプリケーションはそのディスク・ブロックのコピーを,ディスクから新しく読み取らなければなりません。
プログラムでバッファを変更する場合は必ず,変更したバッファをディスクに書き込み,バージョン番号をインクリメントして,対応するロックを NL モードに変換します。このようにすることで,次のプロセスが同じバッファのローカル・コピーを使おうとした場合,バージョン番号が異なることに気づき,キャッシュしてあるバッファ (無効になった) を使わずに,ディスクから最新のコピーを読み取ります。
9.7.2 ブロック通知を使う方法
ブロック通知は,現在ロックが許可されているプロセスに,他のプロセスが共存できないモードで同じリソースにアクセスしようとしてキューに置かれたことを通知するために使用します。
ローカル・バッファ・キャッシングを行う際に,ブロック通知を使う方法は 2 つあります。1 つは,バッファへの書き込みを遅らせる方法で,もう 1 つは,ロック値ブロックを使わずにローカル・バッファ・キャッシングを代替する方法です。
9.7.2.1 バッファへの書き込みの延期
ローカル・バッファ・キャッシングを行っているときには,EX モードのロックを解放する前に,バッファへの変更をディスクに書き込まなければなりません。特に短時間に多くの変更が見込まれる場合は,変更処理を行っている間 EX モードでロックしておき,バッファへの書き込みを 1 回だけにすると,ディスクへの入出力を減らすことができます。
しかし,これでは,その間は他のプロセスが同じディスク・ブロックを使えなくなります。EX モードでロックしているプロセスに,ブロック通知ができれば,このような事態は避けられます。この通知では,ロックしているプロセスに,他のプロセスで同じディスク・ブロックを使う必要があることを知らせます。この通知を受け取った場合,EX モードでロックしているプロセスは,バッファをディスクに書き込んで,ロック・モードを NL モードに変換することができます。これにより,他のプロセスがそのディスク・ブロックにアクセスできるようになります。ただし,同じディスク・ブロックにアクセスしたいプロセスが他にない場合は,最初のプロセスは変更を何度も行ってから,書き込みを 1 回だけで済ませることができます。
注意
ブロック通知を受け取ったプロセスは,次のブロック通知を受け取れるように,ロックを変換しなければなりません。
ブロック通知を使ってローカル・バッファ・キャッシングを実行する場合,バッファへの処理が終わったプロセスは,PR モードまたは EX モードのロックを NL モードに変換しません。代わりに,他のプロセスが共存できないモードで同じリソースをロックしようとするたびに,ブロック通知を受け取るようにします。この方式では,次にそのプロセスがバッファを使おうとしたときではなく,別の書き込みプロセスでこのバッファが必要になったときに,このプロセスでキャッシュしているバッファが無効であることが通知されます。
9.7.3 バッファのキャッシング方式の選択
ローカル・バッファ・キャッシングを実行する場合,バージョン番号を使う方法と,ブロック通知のどちらを使えばよいかは,アプリケーションの性格に依存します。バージョン番号を使用するアプリケーションでは,ロック変換が増え,ブロック通知を使用するアプリケーションでは,通知の配信が増えます。これらの方式は共存できることに注意してください。一方の方式を使っているプロセスがあって,同時に,もう一方の方式を使っているプロセスがあるということが可能です。一般的には,ブロック通知は,衝突が少ない環境で選択し,バージョン番号は衝突が多い環境で選択します。組み合わせて使ったり,必要に応じて切り替えて使うこともできます。
組み合わせて使うときに,プロセスが短時間にバッファの内容を再利用すると予測される場合は,ブロック通知を使い,すぐにバッファの内容を再利用すると予測できない場合は,バージョン番号を使います。
必要に応じて切り替えて使う場合,アプリケーションはブロック通知と変換の発生頻度を評価します。ブロック通知が頻繁に届く場合,バージョン番号を使う方式に切り替え,変換がたくさん発生し,キャッシュされたコピーが長時間変更されず有効な場合は,ブロック通知を使う方式に切り替えます。
例として,あるプロセスがデータベースの状態を絶えず表示し続け,他のプロセスが時々このデータベースをアップデートする場合を考えてみます。バージョン番号を使った場合,表示するプロセスはデータベースのコピーが有効かどうか,ロック変換を行って常に検証しなければなりません。これに対し,ブロック通知を使った場合,表示するプロセスは,データベースがアップデートされるたびに通知を受けます。ただし,アップデートが頻繁に行われる場合は,ブロック通知を頻繁に配信するよりは,バージョン番号を使う方式の方が効率的です。
9.8 分散ロック・マネージャの関数のコーディング例
次のプログラム例では,ネームスペースに加わり,そのネームスペースのリソースの初期ロックを取得するためにアプリケーションが使用する基本的な手法について説明します。また,ロック変換,ロック値ブロックの使用方法,ブロック通知ルーチンの使用方法といった DLM の主要な概念についても,この中で具体的に説明します。
例 9-1
に示すプログラム
api_ex_master.c
および
api_ex_client.c
は,ディレクトリ
/usr/examples/cluster
にあり,TCRMAN
xxx
サブセットがインストールされている場合は,同じクラスタ・メンバ上,または異なるクラスタ・メンバ上で,並列に実行することができます。どちらのプログラムも,同じユーザ ID (UID) を持つアカウントから実行しなければなりません。また,プログラム
api_ex_master.c
を先に実行しなければなりません。
このプログラムの出力結果は,次のようになります。
% api_ex_master & api_ex_master: grab a EX mode lock api_ex_master: value block read api_ex_master: expected empty value block got <> api_ex_master: start client and wait for the blocking notification to continue % api_ex_client & api_ex_client: grab a NL mode lock api_ex_client: value block read api_ex_client: expected empty value block got <> api_ex_client: converting to NL-->EX to get the value block. api_ex_client: should see blocking routine run on master api_ex_master: blk_and_go hold the lock for a couple of seconds api_ex_master: blk_and_go sleeping api_ex_master: blk_and_go sleeping api_ex_master: blk_and_go sleeping api_ex_master: blk_and_go setting done api_ex_master: now convert (EX-->EX) to write the value block <abc> api_ex_master: blkrtn: down convert to NL api_ex_master: waiting for blocking notification api_ex_master: trying to get the lock back as PR to read value block api_ex_client: blkrtn: dequeue EX lock to write value block <> api_ex_client: hold the lock for a couple of seconds api_ex_client: sleeping api_ex_client: sleeping api_ex_client: sleeping api_ex_client: value block read api_ex_client: expected <abc> got <abc> api_ex_client: sleeping waiting for blocking notification api_ex_client: done api_ex_master: value block read api_ex_master: expected <efg> got <efg> api_ex_master done
/******************************************************************** * * * api_ex_master.c * * * ********************************************************************/ /* cc -g -o api_ex_master api_ex_master.c -ldlm */ #include <assert.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <signal.h> #include <sys/dlm.h> char *resnam = "dist shared resource"; char *prog; int done = 0; #ifdef DLM_DEBUG int dlm_debug = 2; #define Cprintf if(dlm_debug)printf #define Dprintf if(dlm_debug >= 2 )printf #else /* DLM_DEBUG */ #define Cprintf ; #define Dprintf ; #endif /* DLM_DEBUG */ void error(dlm_lkid_t *lk, dlm_status_t stat) { printf("%s: lock error %s on lkid 0x%lx\n", prog, dlm_sperrno(stat), lk); abort(); } void blk_and_go(callback_arg_t x, callback_arg_t y, dlm_lkid_t *lk, dlm_lkmode_t blkmode) { int i; printf("%s: blk_and_go hold the lock for a couple of seconds\n", prog); for (i = 0; i < 3; i++) { printf("%s: blk_and_go sleeping\n", prog); sleep(1); } printf("%s: blk_and_go setting done\n", prog); /* done waiting */ done = 1; [13] } void blkrtn(callback_arg_t x, callback_arg_t y, dlm_lkid_t *lk, dlm_lkmode_t blkmode) { dlm_status_t stat; Cprintf("%s: blkrtn: x 0x%lx y 0x%lx lkid 0x%lx blkmode %d\n", prog, x, y, *lk, blkmode); printf("%s: blkrtn: down convert to NL\n", prog); if ((stat = dlm_cvt(lk, DLM_NLMODE, 0, 0, 0, 0, 0, 0)) != DLM_SUCCESS) error(lk, stat); [16] /* let waiters know we're done */ done = 1; } main(int argc, char *argv[]) { int resnlen, i; dlm_lkid_t lkid; dlm_status_t stat; dlm_valb_t vb; dlm_nsp_t nsp; /* this prog must be run first */ /* first we need to join a namespace */ if ((stat = dlm_nsjoin(getuid(), &nsp, DLM_USER)) != DLM_SUCCESS) { [1] printf("%s: can't join namespace\n", argv[0]); error(0, stat); } prog = argv[0]; /* now let DLM know what signal to use for blocking routines */ dlm_set_signal(SIGIO, &i); [2] Cprintf("%s: dlm_set_signal: i %d\n", prog, i); resnlen = strlen(resnam); [3] Cprintf("%s: dlm_set_signal: i %d\n", prog, i); printf("%s: grab a EX mode lock\n", prog); stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid, DLM_EXMODE, &vb, (DLM_VALB | DLM_SYNCSTS), 0, 0, blk_and_go, 0); [4] /* * since we're the only one running it * had better be granted SYNC status */ if(stat != DLM_SYNCH) { [5] printf("%s: dlm_lock failed\n", prog); error(&lkid, stat); } printf("%s: value block read\n", prog); printf("%s: expected empty value block got <%s>\n", prog, (char *)vb.valblk); if (strlen((char *)vb.valblk)) { [6] printf("%s: lock: value block not empty\n", prog); error(&lkid, stat); } printf("%s: start client and wait for the blocking notification to continue\n", prog); while (!done) sleep(1); [7] done = 0; /* put a known string into the value block */ (void) strcat((char *)vb.valblk, "abc"); [14] printf("%s: now convert (EX-->EX) to write the value block <%s>\n", prog, (char *)vb.valblk); /* use a new blocking routine */ stat = dlm_cvt(&lkid, DLM_EXMODE, &vb, (DLM_VALB | DLM_SYNCSTS), 0, 0, blkrtn, 0); [15] /* * since we own (EX) the resource the * convert had better be granted SYNC */ if(stat != DLM_SYNCH) { printf("%s: convert failed\n", prog); error(&lkid, stat); } printf("%s: waiting for blocking notification\n", prog); while (!done) sleep(1); printf("%s: trying to get the lock back as PR to read value block\n", prog); stat = dlm_cvt(&lkid, DLM_PRMODE, &vb, DLM_VALB, 0, 0, 0, 0); [19] if (stat != DLM_SUCCESS) { printf("%s: error on conversion lock\n", prog); error(&lkid, stat); } printf("%s: value block read\n", prog); printf("%s: expected <efg> got <%s>\n", prog, (char *)vb.valblk); /* compare to the other known string */ if (strcmp((char *)vb.valblk, "efg")) { printf("%s: main: value block mismatch <%s>\n", prog, (char *)vb.valblk); error(&lkid, stat); [23] } printf("%s done\n", prog); [24] exit(0); } /******************************************************************** * * * api_ex_client.c * * * ********************************************************************/ /* cc -g -o api_ex_client api_ex_client.c -ldlm */ #include <assert.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <signal.h> #include <sys/dlm.h> char *resnam = "dist shared resource"; char *prog; int done = 0; #ifdef DLM_DEBUG int dlm_debug = 2; #define Cprintf if(dlm_debug)printf #define Dprintf if(dlm_debug >= 2 )printf #else /* DLM_DEBUG */ #define Cprintf ; #define Dprintf ; #endif /* DLM_DEBUG */ void error(dlm_lkid_t *lk, dlm_status_t stat) { printf("\t%s: lock error %s on lkid 0x%lx\n", prog, dlm_sperrno(stat), *lk); abort(); } /* * blocking routine that will release the lock and in doing so will * write the resource value block. */ void blkrtn(callback_arg_t x, callback_arg_t y, dlm_lkid_t *lk, dlm_lkmode_t blkmode\ ) { dlm_status_t stat; dlm_valb_t vb; int i; Cprintf("\t%s: blkrtn: x 0x%lx y 0x%lx lkid 0x%lx blkmode %d\n", prog, x, y, *lk, blkmode); printf("\t%s: blkrtn: dequeue EX lock to write value block <%s>\n", prog, (char *)vb.valblk); printf("\t%s: hold the lock for a couple of seconds\n", prog); for (i = 0; i < 3; i++) { printf("\t%s: sleeping\n", prog); sleep(1); } /* make sure its clean */ bzero(vb.valblk, DLM_VALBLKSIZE); /* write something different */ (void) strcat((char *)vb.valblk, "efg"); [20] if((stat = dlm_unlock(lk, &vb, DLM_VALB)) != DLM_SUCCESS) error(lk, stat); [21] /* let waiters know we're done */ done = 1; } main(int argc, char *argv[]) { int resnlen, i; dlm_lkid_t lkid; dlm_status_t stat; dlm_nsp_t nsp; dlm_valb_t vb; /* first we need to join a namespace */ if ((stat = dlm_nsjoin(getuid(), &nsp, DLM_USER)) != DLM_SUCCESS) { printf("\t%s: can't join namespace\n", argv[0]); error(0, stat); [8] } prog = argv[0]; /* now let DLM know what signal to use for blocking routines */ dlm_set_signal(SIGIO, &i); Cprintf("\n\t%s: dlm_set_signal: i %d\n", prog, i); [9] resnlen = strlen(resnam); Cprintf("\t%s: resnam %s\n", prog, resnam); printf("\n\t%s: grab a NL mode lock\n", prog); stat = dlm_lock(nsp, (uchar_t *)resnam, resnlen, 0, &lkid, DLM_NLMODE, &vb, (DLM_VALB | DLM_SYNCSTS), 0, 0, 0, 0); /* NL mode better be granted SYNC status */ if(stat != DLM_SYNCH) { [10] printf("\t%s: dlm_lock failed\n", prog); error(&lkid, stat); } /* should be nulls since master hasn't written anything yet */ printf("\t%s: value block read\n", prog); printf("\t%s: expected empty value block got <%s>\n", prog, (char *)vb.\ valblk); if (strlen((char *)vb.valblk)) { [11] printf("\t%s: value block not empty\n", prog); error(&lkid, stat); } done = 0; printf("\t%s: converting to NL->EX to get the value block.\n", prog); printf("\t%s: should see blocking routine run on master\n", prog); stat = dlm_cvt(&lkid, DLM_EXMODE, &vb, DLM_VALB, 0, 0, blkrtn, 0); [12] if(stat != DLM_SUCCESS) { printf("\t%s: dlm_cvt failed\n", prog); error(&lkid, stat); } /* should have read what master wrote, "abc" */ printf("\t%s: value block read\n", prog); printf("\t%s: expected <abc> got <%s>\n", prog, (char *)vb.valblk); if (strcmp((char *)vb.valblk, "abc")) { [17] printf("\t%s: main: value block mismatch <%s>\n", prog, (char *)vb.valblk); error(&lkid, stat); } /* now wait for blocking from master */ printf("\t%s: sleeping waiting for blocking notification\n", prog); while (!done) sleep(1); [18] printf("\t%s: done\n", prog); [22] exit(0); }
プログラム
api_ex_master.c
は,dlm_nsjoin
関数を呼び出して,ロックを要求するリソースのネームスペースに加わります。このネームスペースは,現在のプロセスの UID です。これは
getuid
システム・コールで取得しています。このネームスペースは,DLM_USER
パラメータで示されるとおり,実効 UID がリソースの所有者の UID と同じプロセスにアクセスを許すネームスペースです。正常終了の場合,この関数は,nsp
パラメータで示されるメモリ位置に,ネームスペース・ハンドルを返します。
[例に戻る]
プログラム
api_ex_master.c
は,dlm_set_signal
関数を呼び出して,DLM が,このプロセスに完了通知およびブロック通知を送る際に,SIGIO シグナルを使うように指定しています。
[例に戻る]
プログラム
api_ex_master.c
は,次に呼び出す
dlm_lock
関数に渡すために,リソース名の長さを取得します。リソースの名前は
dist shared resource
です。
[例に戻る]
プログラム
api_ex_master.c
は,ネームスペース
uid
のリソース
dist shared resource
を,排他モード (DLM_EXMODE
) でロックするために,dlm_lock
関数を呼び出します。ネームスペース・ハンドル,リソース名,およびリソース名の長さは,すべて必須パラメータとして渡されます。
DLM_SYNCSTS
フラグで指示しておくと,ロック要求がすぐに許可された場合,DLM は
DLM_SYNCH
状態を返します。関数の呼び出しに成功すると,DLM は,lkid
パラメータで指定されたメモリ位置に,排他モード (EX) ロックのロック ID を返します。
この関数には
DLM_VALB
フラグ,およびリソースのロック値ブロックの内容を書き込むまたは読み取るメモリ位置を指定します。DLM は,dlm_lock
関数を呼び出して要求したロックが許可された場合に,リソースのロック値を,このメモリ位置にコピーします。最後に,ブロック通知ルーチン
blk_and_go
を指定します。DLM は,ロックが許可された後,このリソースに対する他のロック要求がブロックされたときに,このルーチンを呼び出します。
[例に戻る]
プログラム
api_ex_master.c
は,dlm_lock
関数から返された状態値を調べます。状態値が
DLM_SYNCH
状態 (dlm_lock
関数を呼び出す際に
DLM_SYNCSTS
フラグを指定した場合の,成功の条件値) でない場合は,ロック要求が許可されるのを待たなければなりません。今回は,このロックを要求しているプログラムが他に実行されていないので,このような状況は発生しません。
[例に戻る]
プログラム
api_ex_master.c
は,DLM が
vb
パラメータで指定されたメモリ位置に書き込んだ,値ブロックの内容が空であることを検証します。
[例に戻る]
プログラム
api_ex_master.c
は,ユーザがプログラム
api_ex_client.c
を起動するのを待ちます。このプログラムは,dist shared resource
をロックしている排他モード (DLM_EXMODE
) のロックのために,プログラム
api_ex_client.c
で出された同じリソースへのロック要求がブロックされたというブロック通知を受け取ったときに再開されます。
[例に戻る]
プログラム
api_ex_client.c
を起動すると,uid
ネームスペースに加わるために
dlm_nsjoin
関数を呼び出します。このネームスペースは,プログラム
api_ex_master.c
を実行しているプロセスが,前に加わっていたのと同じものです。
[例に戻る]
プログラム
api_ex_client.c
では,プログラム
api_ex_master.c
と同じように,dlm_set_signal
関数を呼び出して,DLM が,完了通知およびブロック通知をこのプロセスに送る際に,SIGIO シグナルを使うように指示します。
[例に戻る]
プログラム
api_ex_client.c
では,dlm_lock
関数を呼び出して,api_ex_master.c
を実行しているプロセスが既に排他モードでロックしているリソースに,ヌル・モード (DLM_NLMODE
) のロックをかけます。DLM_SYNCSTS
フラグで指示しておくと,ロック要求がすぐに許可された場合,DLM は
DLM_SYNCH
状態を返します。ヌル・モード (NL) のロックは,この前に許可されている排他モードのロックと共存できるため,このロック要求はすぐに許可されます。この関数ではまた,DLM_VALB
フラグとロック値ブロックへのポインタも指定しています。DLM は
dlm_lock
で要求したロックが許可された場合,リソースのロック値をこのメモリ位置にコピーします。
[例に戻る]
プログラム
api_ex_client.c
は,DLM が,vb
パラメータで指定されたメモリ位置に書き込んだ値ブロックの内容を調べます。値ブロックは,プログラム
api_ex_master.c
がまだ書き込んでいないため,空でなければなりません。
[例に戻る]
プログラム
api_ex_client.c
は,dlm_cvt
関数を呼び出して,このプロセスがリソースにかけているヌル・モードのロックを,排他モードに変換します。ここで,ブロック通知ルーチン
blkrtn
を指定します。プログラム
api_ex_master.c
を実行しているプロセスが,既にこのリソースに排他ロックをかけているので,そのプロセスは,プログラム
api_ex_client.c
のロック変換要求をブロックします。しかし,プログラム
api_ex_master.c
がかけている排他モードのロックで,ブロック通知ルーチンを指定しているため,DLM は,SIGIO シグナルを使って,プログラム
api_ex_master.c
を実行しているプロセスにブロック通知を送り,ブロック通知ルーチン (blk_and_go
) をトリガします。
[例に戻る]
blk_and_go
ルーチンは 3 秒間スリープし,その後,done
フラグを立てます。これにより,プログラム
api_ex_master.c
が再開されます。
[例に戻る]
プログラム
api_ex_master.c
は,そのリソースの値ブロックのローカル・コピーに,文字列
abc
を書き込みます。
[例に戻る]
プログラム
api_ex_master.c
は,ロック値ブロックに書き込むために
dlm_cvt
関数を呼び出します。これを行うために,リソースにかけている排他モードのロックを,排他モード (DLM_EXMODE
) に変換します。このとき,関数にはパラメータとして,ロック ID,値ブロックのコピーのあるメモリ位置,および
DLM_VALB
フラグを指定します。DLM_SYNCSTS
フラグで指示しておくと,ロック要求がすぐに許可されたとき,DLM は
DLM_SYNCH
状態を返します。このプロセスは既に排他モードでリソースをロックしているので,このロック変換要求はすぐに許可されるはずです。
dlm_cvt
関数には,ブロック通知ルーチンとして
blkrtn
ルーチンも指定します。リソースにかけている,この排他モードのロックで,プログラム
api_ex_client.c
から出されるロック変換要求がブロックされるので,DLM はこのブロック通知ルーチンをすぐに呼び出します。
[例に戻る]
プログラム
api_ex_master.c
の
blkrtn
ルーチンが起動され,すぐに
dlm_cvt
関数を呼び出して,リソースにかけているロックを,排他モードからヌル・モードに下位変換します。この呼び出しは,すぐに正常終了します。
[例に戻る]
この変換が実行されるとすぐに,プログラム
api_ex_client.c
のロック変換要求が正常終了します。これは,プログラム
api_ex_master.c
を実行しているプロセスがかけたヌル・モードのロックが,プログラム
api_ex_client.c
を実行しているプロセスが今取得した排他モードのロックと共存できるためです。ヌル・モードのロックを排他モードに上位変換する際,DLM は,プログラム
api_ex_client.c
を実行しているプロセスに,リソースのロック値ブロックをコピーします。このとき,プログラム
api_ex_client.c
は,プログラム
api_ex_master.c
が前にリソースのロック値ブロックに書き込んた文字列
abc
を受け取ります。
[例に戻る]
プログラム
api_ex_client.c
は,スリープ状態に入ってブロック通知を待ちます。
[例に戻る]
プログラム
api_ex_master.c
は,リソース
dist shared resource
のロックを下位変換してから,スリープ状態にありましたが,ここで
dlm_cvt
関数を呼び出して,リソースにかけているヌル・モードのロックを保護読み取りモード (DLM_PRMODE
) に変換します。プログラム
api_ex_client.c
を実行しているプロセスが,既に排他モードでこのリソースをロックしているので,プログラム
api_ex_master.c
のロック変換要求はこのロックによってブロックされることになります。つまり,排他モードのロックと保護読み取りモードのロックは共存できません。ただし,プログラム
api_ex_client.c
がかけている排他モードのロックでブロック通知ルーチンが指定されているため,DLM は,SIGIO シグナルを送信して,プロセスにブロック通知を配信し,そのプロセスのブロック通知ルーチン (blkrtn
) をトリガします。
[例に戻る]
プログラム
api_ex_client.c
のブロック通知ルーチン
blkrtn
は,数秒間スリープ状態になり,その後,そのリソースの値ブロックのローカル・コピーに文字列
efg
を書き込みます。
[例に戻る]
プログラム
api_ex_client.c
のブロック通知ルーチン
blkrtn
は,dlm_unlock
関数を呼び出して,このリソースのロックを解放します。リソースのロック値ブロックのローカル・コピーのアドレス,および
DLM_VALB
フラグを指定して,DLM に対し,許可されるモードが保護書き込み (DLM_PWMODE
) または排他 (DLM_EXMODE
) の場合は,値ブロックのローカル・コピーを,リソースの値ブロックに書き込むよう要求します。このプログラムでは,許可されるモードは
DLM_EXMODE
なので,値ブロックのローカル・コピーがリソースのロック値ブロックに書き込まれます。
[例に戻る]
プログラム
api_ex_client.c
が完了し終了します。
[例に戻る]
プログラム
api_ex_client.c
を実行しているプロセスがリソースのロックを解放すると,すぐにプログラム
api_ex_master.c
のロック変換要求が正常終了します。ヌル・モードのロックを保護読み取りモードに上位変換する際に,DLM は,リソースのロック値ブロックを,プログラム
api_ex_master.c
を実行しているプロセスにコピーします。このとき,プログラム
api_ex_master.c
は,前にプログラム
api_ex_client.c
がリソースのロック値ブロックに書き込んだ文字列
efg
を受け取ります。
[例に戻る]
プログラム
api_ex_master.c
が完了し終了します。
[例に戻る]