一人がその文書を開き、書き終わってから保存するなら、編集の実体と保存は同じものでよいです。分かれ始めるのは、複数人が同時に開き、それぞれの入力が相手へ届き、あとから開く人は保存されたものを読むときです。保存の行を編集の正本にすると、後から書いた方が先の入力を消します。開いた瞬間の空の文書を正本として書くと、同期が終わる前の空白が、すでにあった中身を消します。開いている人へ更新が届く経路と、落ちたあとに読み返すための保存は、失敗の条件が違います。
この構成では、開いているあいだの実体は、競合なく合流する文書です。保存は、その文書の写しを別に置きます。同期が終わる前は、空の文書を見せませんし、空のまま書きません。更新は、その文書を開いている接続のすべてに届けます。
課題
同時に開いている人が二人いると、保存の一行を取り合います。それぞれの手元が全文を書き戻すと、後の書き込みが先の編集を上書きします。片方が保存に失敗すると、その人の手元には残っていて、次に開いた人の文書にはありません。
開いた直後の手元の文書は空です。空をそのまま見せると、読む人は文書が空だったと思います。そこへ入力すると、空から始まった編集が、すでにあった中身と合流します。合流の結果が空に近いものになることもあります。見せなくても、裏でその空を保存すれば、保存してあった中身は空白で置き換わります。同期の相手に届かないときにこれを行うと、相手が持っていた文書を、届かなかった側の空が消します。
更新を保存経由で配ると、開いている人は、保存が終わるまで相手の入力を見ません。保存が遅いと、編集が遅いように見えます。保存が失敗すると、編集も失敗したように見えます。見たいのは、開いている接続のあいだで更新が届いたかであり、写しが残ったかではありません。
逆に、メモリの上だけに文書を持ち、保存を持たないと、プロセスが落ちた瞬間に合流の結果が消えます。平文から作り直すと、構造や、まだ平文に落ちていない合流が落ちます。毎回平文から組み直すと、同時に開いていた人の状態と、次に開いた人の状態が別の文書になります。
書けない人を、接続から外すと、その人は開いているあいだの更新を見ません。接続は許して、その人の更新も保存まで届かせると、書けない権限が保存の側で穴になります。見ることと、書くことは、同じ接続の上で別の事実です。
切断は、最後の保存の待ちの途中で起きます。待ちだけに頼ると、閉じた直後の入力が写しに残りません。待ちを無くして入力のたびに保存すると、保存が再び編集の経路になります。
制約
この構成が置いた制約は、次のとおりです。
共同編集の正本は、同期を受け持っている側のメモリにある文書です。文書ごとに一つの名前を持ち、その名前を開いている接続は、同じメモリ上の文書を見ます。合流はそこで行います。保存の行では行いません。
保存は別の操作です。符号化された状態と、平文の写しを、最後の変更から短い待ちのあとで書きます。同期の処理は、保存の完了を待ちません。開いている接続への配送も、保存の成否を待ちません。
同期が終わる前の文書は、保存しません。閉じるときも、その場を離れるときも、同じです。空の手元の文書を、届かない同期の代わりに正本へ書いてはいけません。
見せ方も分けます。符号化された状態をすでに持っているときは、接続の前にそれを文書へ適用し、最初の描画を空にしません。持っていないときは、同期が終わるまで平文の写しを見せ、空の共同編集面を文書として出しません。写しが無い、本当に空の文書だけが、空として見えます。
更新は、その文書を開いている接続のすべてに送ります。保存されたあとに取りに来る人へ届ければよい、にはしません。
書けない接続も、開いていれば更新を受け取ります。その接続から来た更新は捨てます。捨てた更新は、メモリ上の文書にも、保存にも届かせません。
符号化された状態がまだ無いときだけ、平文の写しから一度作ります。状態がある文書を、開くたびに平文から作り直しません。
切断のときには、待ちの途中でも、メモリ上の文書を保存しようとします。保存の失敗は記録し、編集の接続をその失敗で切りません。
採らなかった方式
保存の行を正本にする
開いている人が、全文を保存の行へ書き戻す方式です。後の書き込みが先の編集を上書きします。同時に開いていても、相手の入力は、次に行を読み直すまで見えません。保存が競合の解決を兼ねるので、保存の遅さと、編集の正しさが同じになります。
平文の写しは、開いていない人が読むためと、同期の前に見せるために残します。合流の基板にはしません。
同期の前に空の編集面を出す
接続が終わるまで、空の編集面を文書として見せる方式です。読む人は、空だったと思います。入力や、自動の保存が、その空を正本にします。同期の相手に届かないとき、この保存は、あった中身を空白で置き換えます。
すでに符号化された状態を持っているのに、それを適用する前に空を描く方式も同じです。持っている状態は、接続の前に適用します。持っていないときだけ、平文の写しを代わりに見せます。空は、写しも状態も無いときだけです。
見せることと、書かないことは、別のガードにします。空を隠しても、裏で保存すれば消えます。同期が終わるまで、保存の経路はすべて止めます。
保存と配送を同じ書き込みにする
入力のたびに保存し、他の人は保存を読みに来る方式です。開いている接続の更新が、保存の往復を待ちます。保存が失敗すると、相手の手元にも届きません。編集できたことと、写しが残ったことが、同じ成功になります。
この構成では、更新はメモリ上の文書に適用した時点で、開いている接続へ送ります。保存は短い待ちのあとで、別に行います。保存が遅れても、開いている人の見える文書は遅れません。保存が失敗しても、開いている文書は残ります。
一人だけが書くようにロックする
開いているあいだ他の人を読み取り専用にする方式です。上書きは起きません。同時の編集も起きません。届けるべきは、開いている全員の入力が同じ文書へ合流し、その結果が全員に見えることです。ロックは、その問題を避けています。
書けない人を接続から外す方式も採りませんでした。外すと、その人は開いているあいだの更新を見ません。接続は許します。更新の適用だけを捨てます。捨てたものは保存にも届きません。
落ちたら平文から作り直す
符号化された状態を残さず、プロセスが落ちたあとは平文の写しから文書を作る方式です。平文に落ちない構造と、写しより新しい合流が消えます。次に開いた人は、落ちる前に開いていた人と別の文書を見ます。
状態がまだ一度も無い文書だけ、平文から一度作ります。それ以降の正本は、符号化された状態です。開くたびに平文へ戻しません。
採用した形
文書は二つあります。開いているあいだのメモリ上の文書と、あとから読むための保存です。名前は文書につき一つで、接続はその名前に着きます。
更新は、メモリ上の文書から、それを開いている接続のすべてへ流れます。保存の完了は待ちません。同期のあと、写しを保存へ書きます。あとから開く人は、その写しを読みます。同期が終わる前の空には、保存へ向かう矢印がありません。
同期が終わるまでは空を出さない
手元は、接続の前に文書を作ります。符号化された状態をすでに受け取っているなら、接続より前にそれを適用します。最初に見えるものが空になりません。状態を持っていないなら、同期が終わるまで平文の写しを見せます。共同編集の面は、そのあいだ文書として出しません。写しも状態も無いときだけ、空の文書として扱います。
同期が終わった、という通知が来るまで、保存はしません。入力に対する待ちの保存も、閉じるときの保存も、その場を離れるときの保存も、同じ旗を見ます。旗が立っていない文書は、空である可能性があります。空を正本へ書きません。
この旗は、見せるかどうかとは別に持ちます。写しを見せて空を隠していても、保存が先に走れば、隠した意味がありません。届かない同期に対して空を書いたことが、あった中身を消す条件でした。
更新は、開いている接続へ送る
接続が付くと、その文書の今の状態を受け取ります。その後の更新は、メモリ上の文書に適用し、同じ名前を開いている他の接続へ送ります。保存が終わるのを待ちません。開いていない人は、次に開いたときに、保存された状態を読みます。開いている人の経路と、あとから開く人の経路は別です。
書けない接続も、更新は受け取ります。だから、開いているあいだの文書は見えます。その接続から来た更新は、捨てます。メモリ上の文書にも、保存の写しにも届きません。
書けない接続も、同じ文書に着けます。他の接続の更新は受け取るので、開いているあいだの文書は見えます。その接続から来た更新は、適用する前に捨てます。メモリ上の文書が変わらないので、他の接続へも、保存へも届きません。書けないことは、見えないことではありません。
認証は、文書と保存を触る前に済ませます。通らない接続は、文書の中身を受け取りません。通ったあとに、書いてよいかを決めます。ここでも、接続できたことと、書いてよいことは別です。
保存は写しです
最後の変更から短い待ちを置き、符号化された状態と、平文の写しを書きます。待ちは、入力のたびに保存しないためです。同期の処理は、この書き込みを待ちません。書き込みが遅いときに、開いている文書まで遅くしません。
切断のときは、待ちを待たずに、今のメモリ上の文書を保存しようとします。閉じた直後の入力を、待ちの中に置き忘れないためです。この保存が失敗しても、開いている他の接続の文書は消えません。失敗は記録し、次の保存で再び書きます。
平文の写しは、開いていない人が読むもの、同期の前に見せるもの、全文を必要とする他の処理のためのものです。合流には使いません。符号化された状態がすでにある文書を、写しから上書きして開きません。
状態が一度も無い文書だけ、写しから符号化された状態を一度作ります。作ったあとは、メモリ上の文書が正本であり、保存はその写しです。写しの方が新しいように見えても、開いている接続が持っている合流を、写しで取り消しません。
読み取り専用の接続は、受け取った更新を保存し直しません。保存は書き込みです。見るために開いている接続が、見るたびに正本を書くと、書けない権限が保存の側で通ります。
何が解けるようになったか
複数人が同時に開いていても、後の全文の書き込みが先の編集を消しません。合流はメモリ上の文書で行い、開いている接続へはその更新を送ります。保存が遅れていても、開いている人は相手の入力を受け取れます。
同期が終わる前の空は、文書として見せません。状態を持っているときはそれを先に適用し、持っていないときは平文の写しを見せます。同じあいだ、保存もしません。届かない同期に対して、空の手元の文書が正本を消すことはありません。
落ちたあとは、符号化された状態から開きます。平文への作り直しは、状態が一度も無いときだけです。切断のときの保存が、待ちの途中の入力を写しに残します。保存が失敗しても、開いている文書そのものは残ります。
書けない人は、開いているあいだの更新を見られます。その人の更新は、文書にも保存にも届きません。接続できたことと、書いてよいことが、分かれます。
共同編集と保存を一つにすると、同時の入力、空の初期状態、開いている人への配送、落ちたあとの復元が、同じ書き込みの上で同時に満たせなくなります。分けたのは、編集が失敗する条件と、写しが失敗する条件が違うからです。