■ 設計指針

○ giant_lock 保持中は休眠してはいけない。XXX 要解説
当然、I/O も行なってはいけない。
当初の実装で、唯一の例外は、dbq がフルの場合の空き待ちの休眠。これは仕方ない。
gfmdの冗長構成が入った時に、同期スレーブgfmd間へのジャーナル転送も、
giant_lock 保持中に行なわれるようになった。（これは、あまり望ましくない…）

当初の実装では、db_access.h のインターフェースは決して失敗しないものと
していた。現在は、ジャーナルファイルへの書き込み等で失敗する可能性が
あるが、その対策が入ってないのは問題。

手抜きで、ネットワーク送信を、giant_lock を保持したままやっている
ところがある。出力は nonblocking モードで行なっている(筈?)なので、
出力内容がバッファよりも小さければ、休眠しない筈。
一応、/* XXX FIXME too long giant lock */ というコメントで、
全ての箇所をマークしてある筈だが…

○ 全メタデータを、メモリにキャッシュする。
性能優先＋コーディングを簡単にするのが目的。
データは gfmd起動時に全てメモリに読み込み、更新のたびに dbq 経由で DB に
書き出す。初期化を除くと、起動中に DB から読み込みを行なうことはない。

ただし、xattr だけは例外。
理由は、
・xattr としてかなり大きなデータを想定していた
・設計者が他の部分とは別
であるため。
逆に、たとえデータサイズが小さくても、xattr へのアクセスは全くキャッシュ
されておらず、非常に遅いという制限事項があった。現在は、ユーザが指定した
xattr はキャッシュできる。また、gfarm.ncopy は常にキャッシュする。

■ 命名規則

libgfarm とは異り、独立したプログラムなので、ではないので、グローバル
シンボルに gfarm_ や gfs_ をプリフィックスとしてつける必要はない。

(原則として) 構造体ごとにモジュールを作り、構造体の定義は *.c に
書いて、他のモジュールからは参照できなくする。
継承の実装のために、他のモジュールから参照できる必要がある場合には、
構造体の定義を *_impl.h に記載する。
その構造体にアクセスする関数は、関数名のプリフィックスに構造体の
名前をつける。
たとえば、struct host の定義は host.c にあり、モジュール内の関数名は
host_*() となっている。
たとえば、struct abstract_host は、struct host および struct mdhost から
継承するため、構造体定義は abstract_host_impl.h にあり、モジュール内の
関数名は abstract_host_*() となっている。

■ データ構造

struct host, struct user, struct group へのポインタは、
gfmd が動作している間、常に有効であり、継続して利用できる。

ただし、削除されたホスト/ユーザー/グループも存在するので、
場合によっては host_is_valid()/user_is_valid()/group_is_valid() で
現在有効なグループか否かも検査する必要がある。
host_lookup()/user_lookup()/group_lookup() は、valid なものしか返さない
ので、*_lookup() で得たポインタを、giant_lock を保持した範囲内で利用して
いる限りは、valid か否かを検査する必要がない。

ホストに関しては、そのホストが動作中か否かという状態が別にあり、
host_is_up() で検査できる。この host_is_up() については、
giant_lock とは別のロックで保護されているため、giant_lock 保持中で
あっても、host_is_up() を呼び出すたびに別の結果が返ってくることがあり、
そのことを考慮してコーディングする必要がある。

※ struct inode も、現在の実装では gfmd が動作している間、常に有効だが、
　 コーディング規約としては、それを仮定していない。
　 giant_lock を手放している区間を経由して inode を利用する場合は、
   struct inode へのポインタではなく、i-node 番号を用いる必要がある。

■ mutex 一覧

mutex 間の依存関係にループがあってはいけない。デッドロックする。★重要★

同時に複数の mutex の獲得を行なう場合の獲得順序は、すべて文書化が必要。

mutex 獲得順序は、server/gfmd/README 参照
XXX 保守されてない。要更新

・giant_lock -> struct host::replication_mutex -> dbq.mutex
	cf. host_peer_set()

・giant_lock -> dfc_allq.mutex
・giant_lock -> busyq.mutex -> removeq.mutex -> host:back_channel_mutex
	以下の条件の複合による。
	giant_lock -> removeq.mutex
	busyq.mutex -> removeq.mutex
	busyq.mutex -> host:back_channel_mutex (for host_is_up())

○ giant_lock を保持したまま、休眠して良いスレッド

  以下のスレッドのみ、giant_lock を保持したまま休眠しては良い。
  これ以外のスレッドが休眠するのは、依存関係のループを生じる危険があるため
  禁止。

  ・sync_protocol_thread_pool に属するスレッド

○ leaf routine のみで獲得している mutex

   これらの mutex を保持したまま、他の mutex を獲得することはないし、
   してはいけない。

  ・callout_module.mutex

■ スレッド一覧

スレッド間の依存関係にループがあってはいけない。デッドロックする。★重要★

thrpool_add_job() する場合、
thrpool_add_job() を呼び出すスレッドは、
thrpool_add_job() されるスレッドプールに、
依存してしまう。
すなわち、thrpool_add_job() されるスレッドプールが満杯の場合、
thrpool_add_job() するスレッドは休眠する。

スレッドプールに属するスレッドが、
自身のスレッドプールに対して thrpool_add_job() するのも駄目。★重要★
そのスレッドプールの全スレッドが、
たまたま全て同時に自スレッドプールに対して thrpool_add_job() すると、
プールに空きがないので、全て休眠し、そのまま寝たきりになる。
もっとも通常の負荷であれば、gfarm_metadb_job_queue_length の長さがある
キューによって緩衝されるため、このデッドロックは発生しない。高負荷で
キューが一杯の時に上記の状況が発生するとまずい。

スレッドプール間の依存関係にループがあるのも駄目。★重要★
back_channel_send_thread_pool は、通信を解して暗に, 
back_channel_recv_thread_pool に依存しているので要注意。★重要★

依存関係の図は
doc/internal/png/gfmd-thread-dependency.png
にあるが、この図で、
・矢印を丸(スレッド)を辿った時、スレッド間がループになっていてはいけない。
・矢印で箱(スレッドプール)を辿った時、プール間がループになっていてはいけない。
・同一の箱(同一のスレッドプール)の中にあるスレッド間に、
  thrpool_add_job()の矢印関係があってはいけない。

○ main スレッド: accepting_loop() 

   TCP 接続待ちを行なうのが役割。

○ create_detached_thread() で作成される、独立したスレッド。

　・シグナル監視用 sigs_handler()

　・callout 時刻監視用 callout_main() × CALLOUT_NTHREADS

    callout_main() が thrpool_add_job() するスレッドプールの数だけ
    callout_main() スレッドを用意した方が良い。さもないと、あるスレッド
    プールが満杯となったとき、関係ないスレッドの処理まで遅延してしまう。
    現在は、back_channel_send_thread_pool のみであるため、CALLOUT_NTHREADS == 1

    callout_reset() しているのは、back_channel_recv_thread_pool に属する
    gfs_async_client_status_result() だが、callout_reset() は
    cond_signal() しているだけなので、callout_reset() では依存は起きない。

  ・db_thread()

    dbq の要素を取得して db へ反映するスレッド。
    metadata-replication が有効な場合、dbq に追加される要素は参照系の
    操作に限られる。

　・ネットワーク受信監視用 peer_watcher() × 1

    peer_watcher() が thrpool_add_job() するスレッドプールの数だけ、
    peer_watcher() スレッドを用意した方が良い。さもないと、あるスレッド
    プールが満杯となったとき、関係ないスレッドの処理まで遅延してしまう。
    現在、sync_protocol_thread_pool と back_channel_recv_thread_pool の 2つ
    あるため peer_watcher() スレッドも 2つにしたいが、現行の peer_watcher() の
    実装がそれを許してなさそうなため、1つのまま。
	→ peer_watcher() を 2スレッドで動かせるように変更する予定。XXX

    XXX DEADLOCK
    現状、以下の理由で、deadlock の危険がある。

    peer_watcher() が呼び出す 2つのスレッドプール、sync_protocol_thread_pool
    および back_channel_recv_thread_pool は、back_channel_send_thread_pool の
    ように、非同期に大量の送信を一度に行なって詰まる心配はない。しかし、
    sync_protocol_thread_pool は back_channel_send_thread_pool に依存し
    ているため、back_channel_send_thread_pool の側の問題で、peer_watcher() に
    よる sync_protocol_thread_pool に対する thrpool_add_job() が詰まる
    可能性は残る。
    peer_watcher() が 1スレッドのみだと、peer_watcher() 自体が止まって
    しまうため、back_channel_recv_thread_pool も巻き添えをくって、止まる。
    その結果、back_channel_recv_thread_pool に依存している
    back_channel_send_thread_pool も動かなくなり、依存関係のループとなって、
    deadlock する。

  ・peer_closer()

  ・旧backend_protocolの場合、各filesystem nodeに対し remover() × 大量

    廃止して、back_channel_recv_thread_pool を使うようにする予定。

  − metadata-replicationが有効な場合

    <master gfmd において>

    ・gfmdc_journal_asyncsend_thread()
      (synchronous_replication=disable の場合のみ)

      journal record を slave gfmd へ送信するスレッド。複数の slave gfmd が
      存在する場合は、順番に送信する(並列に送信しない)。
      synchronous_replication はデフォルトで enable である。
      このスレッドは slave gfmd が master gfmd に昇格するときにも生成される。

    ・db_journal_store_thread()

      journal file から journal record を読み込んで、DB へ反映するスレッド。
      metadata-replication が有効の場合、変更操作についての db_access の
      関数では dbq を使用せずに journal file へ更新情報を書き出す。
      このスレッドは slave gfmd が master gfmd に昇格するときにも生成される。

    <slave gfmd において>

    ・gfmdc_connect_thread()

      gfmd channel を接続するスレッド。master gfmd へ接続するまで再試行し、
      接続後に切断すると、再接続を試みる。
      master gfmd へ昇格するとき停止する。

    ・db_journal_recvq_thread()

      master gfmd から受け取った journal record を保持しているリストから journal
      record を取り出して、journal file へ書き出すスレッド。
      gfmd channel は async protocol なので、master gfmd が journal record を
      送信した順番に slave gfmd が受信するとは限らない。そのため、slave gfmd は
      受信した journal record を一度メモリ上のリストへ保留する。
      ところで、journal record をリストへ追加するスレッドは、
      gfmdc_recv_thread_pool の gfmdc_server_journal_send() である。
      リストの要素数が一定値を超え、journal file reader が読み込み待ち状態でない
      (すなわち db_journal_apply_thread() が journal record を反映中) である場合
      は、gfmdc_server_journal_send() はリストの要素数が一定値以下になるまで待機
      する。このスレッドは、gfmdc_server_journal_send() のスレッドを再開する役割
      がある。
      このスレッドは master gfmd へ昇格するとき、journal file へ書き出すことが
      可能な journal reocrd を全て書き出してから、停止する。

    ・db_journal_apply_thread()

      journal file から読み込んだ情報をメモリと DB へ反映するスレッド。
      master gfmd へ昇格するとき停止する。

○ スレッドプール

プール中の各スレッドの実体は、thrpool_worker()。

　− authentication_thread_pool

    このプールを用いて起動されるのは、以下のスレッド。

    ・try_auth()
      accepting_loop() が thrpool_add_job() する。

　− sync_protocol_thread_pool

    このプールを用いて起動されるのは、以下のスレッド。

    ・protocol_main()
      - try_auth() が、peer_authorized() 経由で、thrpool_add_job() する。
      - peer_watcher() が、thrpool_add_job() する。

　− back_channel_send_thread_pool
  
    このプールを用いて起動されるのは、以下のスレッド。

    ・gfs_async_client_status_request()
      - protocol_main() が、gfm_server_switch_async_back_channel() 経由で、
        thrpool_add_job() する。
      - callout_main() が、thrpool_add_job() する。
	callout に起床を依頼しているのは、back_channel_recv_thread_pool に属する
	gfs_async_client_status_result() だが、callout_reset() を使っているので
	依存は起きない。

    ・gfs_async_client_replication_request_request()
      - protocol_main() が、async_back_channel_replication_request() 経由で、
        thrpool_add_job() する。

　− back_channel_recv_thread_pool

    このプールを用いて起動されるのは、以下のスレッド。

    ・back_channel_main()
      - protocol_main() が、gfm_server_switch_async_back_channel() 経由で、
        thrpool_add_job() する。
      - peer_watcher() が、thrpool_add_job() する。
	これが発生するのは、gfsd からの async RPC request か、あるいは、
	gfmd からの async RPC request に対して gfsd が reply した場合。
	後者の gfmd→gfsd の async RPC request は、back_channel_send_thread_pool
	を用いて行なわれる。このため、async_back_channel_main() の処理が
	詰まると、結果として、back_channel_send_thread_pool までも詰まる
	可能性がある。
	このため、back_channel_recv_thread_pool に属するスレッドは、
	以下のプールに属する資源を待ってはいけない。★重要★
	 * authentication_thread_pool
	 * sync_protocol_thread_pool
	 * back_channel_send_thread_pool
	もし行なうと、依存関係がループを構成し、deadlock の危険が生じる。
	当然、上記のプールに thrpool_add_job() してもいけない。
	また、上記のプールに属する資源待ちの mutex を待ってもいけない。
	したがって、giant_lock() を行なっても駄目。
	ただし、上記のプールと競合するが、それ自身では cond_wait() 等を
	行なわない、リーフの mutex を待つだけなら問題ない。
	また、gfm_async_server_replication_result() は、直接、以下を
	行なってはいけない。
		・結果の処理のための host_replicated()
		・結果の送信 peer_sender_lock() を待つことがある。
				→ 送信側を待つことがある。XXX DEADLOCK

 − metadata-replicationが有効な場合

   − gfmdc_recv_thread_pool

     このプールを用いて起動されるのは、以下のスレッド。

     ・gfmdc_main()

       gfm_server_switch_gfmd_channel() で peer の protocl_handler として
       設定する。db_journal_recvq_thread() でも述べているが、このスレッド
       は gfmdc_server_journal_send() 内で journal record をメモリ上の
       リストへ追加する際に、待機することがある。

   − gfmdc_send_thread_pool

      r5204 時点においては未使用。
      gfmd channel の送信側スレッドプールとして使用する予定であるが、
      r5204 時点ではまだ使用していない。

   − journal_sync_thread_pool
      (master gfmd, synchronous_replication=enableの場合のみ)

     このプールを用いて起動されるのは、以下のスレッド。

     ・gfmdc_journal_send_thread()

       journal record を slave gfmd へ同期的に送信するスレッド。
       async protocol の返信が戻るまで待機する。
       このスレッドは変更操作発生時に gfmd channel で接続中の slave gfmd の
       数だけ並列に動作する。protocol_main() スレッドは
       全ての gfmdc_journal_send_thread() と、gfmdc_journal_file_sync_thread()
       が終了するまで、待機する。
       protocol_main() は giant_lock を取得した状態で待機することに注意が
       必要である。gfmdc_recv_thread_pool のスレッドが async protocol の
       受信を行う際、giant lock を取得してはならない。

       [スレッドの依存関係についての補足]

         図 doc/internal/chart/gfmd-thread-dependency.png では、
         gfmdc_journal_send_thread -> gfmdc_main の依存によって、依存関係の
         ループができているように見えるが、gfmdc_journal_send_thread は
         master gfmd 上、gfmdc_main は slave gfmd 上で動作するため、
	 実際はループになっていない。

     ・gfmdc_journal_file_sync_thread()
       (synchronous_journaling=enable の場合のみ)

       journal file へ fdatasync を実行するスレッドである。
       synchronous_journaling=disable のとき、このスレッドは生成されない。
       デフォルトで synchronous_journaling=enable である。

     ・gfmdc_journal_first_sync_thread()

       gfmd channel 接続直後に slave へ送信するべき journal record が存在
       する場合、このスレッドが連続的に journal record を送信する。
       このスレッドの動作中に、protocol_main() スレッドが db_access から
       変更操作を行った場合、このスレッドの通信先の slave gfmd を除いて、
       gfmdc_journal_send_thread() による journal record の送信が実行される。


■ ファイルの削除について

実際にメモリ上から削除する−inode_remove()を呼ぶ−のは、
以下の条件がすべて成り立った時。

(1) nlink が 0
(2) ファイルがオープンされていない
(3) gfmd 主導の replication が実行中でない

すなわち

	if (inode->i_nlink == 0 && inode->u.c.state == NULL &&
	    (!inode_is_file(inode) || inode->u.c.s.f.rstate == NULL))
		inode_remove(inode);


■ back_channel に関する設計

○ back_channel に必要な資源 (callout など) を、
   (1) peer に帰属させるか
   (2) host に帰属させるか
   (3) back_channel 通信のための構造体を作成し、そこに帰属させるか
  という選択肢がある。
  back_channel 通信終了時の同期処理を考えると、その生存区間が gfmd 起動中
  である (1) か (2) の方が、データ構造自体が消えてしまうため同期用に別領域
  の確保が必要となる (3) よりもプログラムの作りが簡単になる。
  また以下のように peer の方が、host よりも数が多く
	(peer の数 == クライアントの数× 2 ＋ host の数)
  メモリ消費的には (2) の方が有利なので、host に帰属させることにした。
  ただし async については、conn と同様な通信処理時に必要なデータであり、
  back_channel が新しいコネクションに切り替わっているが、
  古いコネクションもまだ残っている場合に使われる可能性を考慮して (1) とする。

・host_receiver_lock() が必要な理由
  受信は peer_watch() から呼び出され、同一 peer なら同一スレッドで動作
  するため、peer 間の受信競合は発生しない。
  host_receiver_lock() している理由は、peer_free_request() との競合回避
  のため。

・host_sender_lock()/_trylock() が必要な理由
  - host_receiver_lock() と同様 peer_free_request() との競合回避
  - 送信の場合は同一 peer でも複数スレッドで競合するのでその回避
  - 送信側が詰まった場合に、同一 peer 宛のスレッドで、スレッドプールが
    埋め尽くされるのを防ぐために host_sender_trylock() で事前検査する。
    gfm_async_server_replication_result() のみは _trylock() ではなく
    _lock() を使っているので、複数のスレッドプールを占有してしまう
    可能性があるが、これは返答であるため、実質的な危険性はかなり低い。

・host_disconnect_request() の呼び出しについて
  複数の back_channel スレッドからや、フォアグラウンドの
  gfm_server_switch_back_channel_common() から同時に呼び出される
  可能性がある。
  特に、フォアグラウンドで切断→フォアグラウンドで再接続→古い back_channel
  が、新しい接続を切断 となるとまずい。
  この対策として、以下のようにする。
   - 引数で peer を渡し、これが異なっていたら、なにもしない。
     フォアグランドから呼び出す時は peer として NULL を渡す。
     この場合には、有無をいわさず切断する。
   - 既に host->peer == NULL なら、なにもしない
  また、host_sender_unlock() や host_receiver_unlock() 時には、既に
  切断されている可能性があるため、引数で peer を渡し、これが異なってい
  たら、なにもしない。
  back_channel.c では、ロックをかけずに host_get_peer() しておき、あとで
		if (peer != NULL) /* to make the race condition harmless */
			host_disconnect_request(host, peer);
  としている部分があるが、これは host_get_peer() 以降、フォアグランドで
  host_peer_set() が行なわれた場合の対策。この場合、GFS_PROTO_STATUS へ
  の返事がないにも関わらず、host_disconnect_request() がこの if 条件の
  ために呼ばれない可能性もわずかにあるが、それは次回の GFS_PROTO_STATUS で
  救済できる。

・ホスト down 対策
  - ・gfs_client_status_request() で、前回からまだ答がなかった
    場合、すなわち host_status_reply_is_waiting() が成り立っていた
    時には、コネクションを切断する。(down 状態となる)
  - 送信処理の時に host_sender_try_lock() で送信可能かどうかを
    確認し、送信が滞留していた場合には、host_peer_busy() で
    コネクションを切断する

■ ファイル複製管理

○ 基本的には、同一の世代番号を持つレプリカは全て同一の内容を持つ。

例外は以下の2種類。

- 更新対象として、書き込み用にオープンされているレプリカ
	これは、全レプリカ中、一つだけ存在しうる。
	gfmd 的には、
		(accmode_to_op(struct file_opening::flag) & GFS_W_OK) != 0
	という条件が成り立っている場合の
		struct file_opening::u.f.spool_host
	が、そのホスト。
	クローズ時に、このレプリカの世代番号を上げて解決する。
- レプリケーション途中のファイル
	レプリケーション先は、転送途中の不完全な内容を持つ。
	この場合、FILE_COPY_IS_VALID(struct file_copy *) が偽となる。

	FILE_COPY_IS_VALID(struct file_copy *) が偽となるのは、これ以外に、
	内容は完全だが、削除途中のレプリカすなわち
	FILE_COPY_IS_BEING_REMOVED(struct file_copy *) が真となるレプリカ
	がある。

	レプリケーション途中に、作成中とものと同じレプリカの作成を要求すると、
	GFARM_ERR_OPERATION_ALREADY_IN_PROGRESS を返す。
	最新世代のレプリカを削除中 (FILE_COPY_IS_BEING_REMOVED()が真) に
	再度そのレプリカの作成を要求すると、GFARM_ERR_DEVICE_BUSY を返す。

○ 基本的には、削除要求が出る (dead_file_copy ができる) のは、旧世代のみ

例外は以下の2種類

- なんらかの理由でレプリケーションが失敗して、その削除を要求する場合
- 「gfrm -h ノード」や「gfrep -x」すなわち gfs_replica_remove_by_file() で
　要求された場合

この2種類の例外については、dead_file_copy に加えて、
FILE_COPY_IS_BEING_REMOVED(struct file_copy *) を真として、管理/監視する。
dead_file_copy の load 時には、このための struct file_copy 設定も必要となる。

■ ファイル複製処理の状態

○ 現行の gfmd 主導リプリケーションの概要

− 契機

・ユーザからの複製要求

・ファイルの更新
  ファイルが更新された場合、それまで複製があったノードに、更新後の複製を配る。

  具体的には、inode_remove_every_other_replicas() で、to_be_replicated に
  配布先を集め、
	remove_replica_entity(〜, &deferred_cleanup)
	file_replicating_new()
	async_back_channel_replication_request()
  としているところが、それ。
  ここで、dead_file_copy は作成しているが、実際に削除要求RPCは行なっていない。
  作成した dead_file_copy は deferred_cleanup として受け取り、
  struct file_replicating に保存し、複製処理完了後に、削除要求を行なう。
  この間、この dead_file_copy は dead_file_copy_mark_kept() により
  保護されている。

  なお、ここで (すなわち schedule_replication() で)
  FILE_COPY_IS_BEING_REMOVED(copy) なノードも更新結果の配布先から除外して
  いるが、この検査はなくても良い。
  なぜなら、配ろうとしているのは新しい世代のレプリカであるため、旧世代の
  レプリカの配送が途中でも、並行して配送できるため。
  ただし、以下のような理由から、この検査を行なっている。
  - 直前に複製に失敗していれば、今回も失敗する可能性が高いだろうと思われる
  - 「gfrm -h ノード」で明示的に削除されたなら、配るべきではない。

− データ構造

複製処理実行中は、!FILE_COPY_IS_VALID() となっている。
複製処理完了時に inode_replicated() から、inode_add_replica(inode, fr->dst, 1)
を呼び出し、valid = 1 に変更している。

複製処理が失敗に終った時には、inode_replicated() から、
inode_remove_replica_gen_deferred(〜, &dfc) を呼び出し、
返ってきた dfc を removal_pendingq_enqueue() に渡して削除要求を出す。
removal_pendingq_enqueue() 側では、以下の処理を行なっている。
失敗した複製処理が最新世代に対するものであれば、
	remove_replica_entity() で dead_file_copy を作成する。(DBにも書く)
	valid == 0 であり、まだ file_copy は DB に書かれてないので、
	DB に対する file_copy の削除は行なわない。
	file_copy は free() する。
	XXX FIXME 削除が完了するまでは、file_copy の free() を行なっては
	いけない。でないと、削除が完了する前に、同一世代番号で新しい複製が
	作成されたり、あるいは truncate の場合には、書き込み用レプリカと
	して選択されてしまう危険まである。その場合、後から届いた削除要求
	によって、誤ってファイルが消されてしまう！
	最新世代の削除は gfrm -h すなわち gfs_replica_remove_by_file()
	すなわち GFM_PROTO_REPLICA_REMOVE_BY_FILE → inode_remove_replica()
	でも行なっている。
	この問題は、以下で認識され、
	    https://sourceforge.net/apps/trac/gfarm/ticket/88
		#88 - race condition between a replication failure
		and another replication
	replicationに関しては、r4657, r4659 で dead_file_copy_remove() を
	導入することにより(効率は悪いが)、一応対策された。
	しかし、r4955 で、上書き時には、それまでレプリカの存在しなかった
	ノードにも同一世代のファイルを作成するケースが生まれたため、
	再度問題になった。
	また、関連して
	    https://sourceforge.net/apps/trac/gfarm/ticket/78
		#78 annoying pgsql error messages about deadfilecopy
	    https://sourceforge.net/apps/trac/gfarm/ticket/144
		#144 INode table in the PostgreSQL backend may not be
		corectly updated
失敗した複製処理が旧世代に対するものであれば、
	単に remove_replica_entity() で dead_file_copy を作成する。(DBにも書く)
	file_copy については存在しないので、何も行なわない。

もし、ファイルの更新があった時に、古い世代について複製処理が実行中で
あれば、古い struct file_copy は削除し、dead_file_copy を作成し、
削除要求も出す。これは inode_remove_every_other_replicas() の
	remove_replica_entity(〜, NULL)
で行なっている。remove_replica_entity() は最終引数が NULL であれば、
削除要求も発行する。
XXX FIXME: race condition
ここで、複製処理は gfsd 側でキューに溜るので古い複製の削除要求が、
古い複製の複製要求よりも先に実行される可能性が、理論的にはありうる。
ただし、この場合、スプールにゴミが残るだけなので、ファイルを失うと
いった問題は起きない。

○ 2.4.0 リリースの頃に、今後の予定として考えていたもの (古い。今はさらに改訂)

replication_pendingq
replication_confirmingq
replication_finishedq
replication_busyq

requested	初期状態
newgen_waiting	世代番号更新待ち
pending		GFS_PROTO_REPLICATION_REQUESTの送信待ちキュー
in_flight	GFS_PROTO_REPLICATION_REQUEST要求送信
replicating	GFS_PROTO_REPLICATION_REQUEST返答受信、
		GFM_PROTO_REPLICATION_RESULT要求の受信待ち
replicated	GFS_PROTO_REPLICATION_REQUEST返答は未受信にも関わらず、
		GFM_PROTO_REPLICATION_RESULT要求を受信した  race condition
		GFS_PROTO_REPLICATION_REQUEST返答の受信待ち
confirming	GFS_PROTO_REPLICATION_REQUEST返答と
		GFM_PROTO_REPLICATION_RESULT要求の両方を受信し、
		GFM_PROTO_REPLICATION_RESULT返答の送信待ちキュー
finished	GFM_PROTO_REPLICATION_RESULT返答を送信した後の
		finalizing 待ちキュー
finalizing	終了処理中
removal_waiting	複製処理に失敗し、不完全なレプリカの削除待ち。
		削除完了までは、copy->valid == 0 な状態は維持している。
replication_waiting
		削除待ち中に、新たなレプリケーションを依頼された。
		この場合、必ずリトライする。
busy		back_channel 送信がbusy状態のため、
		GFS_PROTO_REPLICATION_REQUESTの送信ないし
		GFM_PROTO_REPLICATION_RESULT返答の送信が
		可能になるのを待っている

XXX
○ キャンセルの実装 
kill した場合、プロトコルの同期が失われるので、
gfsd connection cache の purge が必要。

XXX
○ 失敗した場合、可能ならリトライしたい。

XXX 
○ inode:rstate に on going replication が保持されている時、
  inode がリムーブされると、inode は DB から消えるが、メモリ上には残る。
  このとき、file_copy は DB からもメモリからも消えてない。
  この時、gfmd が殺されると、file_copy が orphan になる。

○ ファイル複製に失敗した場合、宛先ホストの不完全な複製は、
  gfmd が dead_file_copy を使って消した方が良いか？
  それとも gfsd が自分で消してから、ファイル複製処理結果を報告する
  のが良いか？

race condition を避ける意味では、gfsd が消した方が簡単。
しかし、その場合、gfsd がクラッシュすると、不完全なレプリカのゴミが
残ってしまうことになる。
そういう意味で、gfmd が消した方が良い。

ただし、gfmd は、消去が完了するまで監視し、その間は incomplete な
レプリカ状態を維持し、競合を避ける必要がある。
なお、この消去完了待ちは、複製作成失敗の時だけ行なえば良いわけではない。
単なるレプリカ削除の場合も、incomplete 状態の維持は必要。
さもないと、それと並行して複製作成が行なわれる可能性がある。

ちなみに gfarm-2.3.0 以前でも、gfmd が消していた。

この件が
http://sourceforge.net/apps/trac/gfarm/ticket/88
https://dev.chubu.sra.co.jp/trac/gfarm/nttcom/ticket/5
https://xi.lab.sphere.ad.jp/trac/gfarm/ticket/62
として問題となった。

○ 複製処理の失敗
	(a1) gfmd が gfsd へ複製要求を出す
	(a2) 複製処理に失敗する
	(a3) gfmd が、複製先の実体を消す要求 (== dead_file_copy) を作る
	(a4) gfmd から gfsd へ要求が伝達され、実体が消える
○ もう一つの複製処理
	(b1) 同じファイルに関し、同一の複製先への複製処理が要求が出る
	(b2) gfsd が複製を作成する
という 2つの処理が、
(a1) → (a2) → (a3) → (a4) → (b1) → (b2)
という順序で実行されれば問題ないが、
(a1) → (a2) → (a3) → (b1) → (b2) → (a4)
という順序で実行される、複製された実体が誤って a4 で消されてしまうのでまずい
対策として、
	複製要求が出た時、同一レプリカに対するdead_file_copyがあったら、
	状態が kept/in_flight/finished/finalizing ならば、
		処理完了まで待つ
	さもなくば
		そのdead_file_copyを消す
という手が考えられる。しかし、処理完了まで待つのはgfmdの内部構造的に
面倒なので、とりあえずの手として、
	状態が kept/in_flight/finished/finalizing のケースについては、
		単に BUSY を返す
という方法もある。

☆ 注意事項:
  async_back_channel_replication_request() を呼ぶ前には、
  dead_file_copy_remove() == GFARM_ERR_NO_ERROR を確認しておく必要がある。
  さもないと、上記 race condition が発生する。

■ RPC プロトコル・ハンドラーについて

gfmd.c:protocol_switch() から呼び出される、各RPC のプロトコル・ハンドラーの
戻り値 gfarm_error_t には、下記のような仕様がある。

	クライアントとの間で通信エラーが発生した場合、
	RPC プロトコル・ハンドラーをその通信エラーコードを返す。
	通信エラーであり、その後は通信不能となるため、
	（資源等の解放後）即座にプロトコル・ハンドラーから帰ればよい。

	クライアントとの間で通信エラーが発生しなかった場合は、
	プロトコル・ハンドラーは、RPC要求の処理結果を返す。
	この場合、クライアントとの通信を中断して終えてはいけない。
	たとえメモリ不足のようなエラーであっても、プロトコル処理を継続
	できるよう、通信を最後まで行なってから、プロトコル・ハンドラー
	から帰るようにする。（継続処理が極めて困難な場合は別途検討する）

	RPC要求の処理結果としてエラーが発生し、かつ通信エラーも生じた場合は、
	通信エラーの方を優先して返す。

	なお、gfm_server_put_reply() は、内部的に
	・通信エラーが起きたら、そのエラーコードを返す。
	・通信エラーが起きてなければ、RPC要求の処理結果を返す。
	という処理が組み込まれているため、
	RPCプロトコル・ハンドラの最後の処理が gfm_server_put_reply()
	である場合には、特に意識しなくても、この仕様が実現される。

プロトコル・ハンドラーの中で、通信エラー(ないしハンドラーからの戻り値)と
RPC要求の処理エラーの２つを、別々の変数に保持したい場合、それぞれ、
以下の変数を、今後は用いることとする。
	e_ret: 通信エラー(ないしハンドラーからの戻り値)
	e_rpc: RPC要求の処理エラー
この場合、もし変数 e は、一時変数として使う。
（ただし、e_ret と e_rpc を区別する必要があることは稀であるし、
　またそれに加えて一時変数 e が必要となることは、さらに稀であるが…）

■ XXX 未記載 TO-DO

・protocol_main() で受けた処理は resuming_thread() での返答待ちになる
  ものがあるが、resume が callback で実現されているため、スレッドプー
  ル資源待ちのデッドロックにはならない。

