EarlgreyTea さんが書きました:
chaika用のgrayスキンはlocalStorage.setItem()で値を書いてエラー発生時はキャッチしてダイアログを出して処理を返しているだけなので、たまたま巻き込まれた側のような気がします。
そこまで行っていれば、話しは早い。
try{localStorage.setItem("key","value");}catch(e){alert("...");} くらいしか書きようがない。
localStorageは、これ以上シンプルなものは無いくらいにシンプルなもので、以下の定義(SQLite Managerで簡単にわかる)。
CREATE TABLE webappsstore2 (scope TEXT, key TEXT, value TEXT, secure INTEGER, owner TEXT)
scope, key, value, secure, owner という5つのカラムの至極単純な表で、
scopeがサイトのURIに対応していてアプリからは見えなくて、アプリは、key, valueのセットを自由に書けるというだけの話。
localStorageというオブジェクトのプロパティ経由での更新を考えると、XPCOMだのがでてきてややこしくなるけれど、
推奨のlocalStorage.setItem()/getItem()方式で考えると、setItem()/getItem()/clear()を呼んでSQLのリクエストをだしているだけ。
webappsstore2 というSQLのTableの管理は、おそらく「localStorage」の中で、mozStorage(SQLiへのインターフェース)を使ってやっているはずだが、
Table単位でのロック絡みだとすると、chaikaとかgray以外のアプリ・サイト以外もダメになるはずだから、
scope単位での「オープン」、「行のロック」、の話?
クリーンなテスト環境では一度も起きていない、負荷が高い時のようである、長い時間使った後である、アプリ側はsetItem()/getItem()/clear()しか出せない、マルチタスクは当たり前でマルチCPUも当たり前、ということなどを考えると、
タイミングによって、localStorageの中でデッドロックが発生してしまう?
と思いつつ、bugzillaをNS_ERROR_STORAGE_BUSYで検索してみたが、バグサマリーにNS_ERROR_STORAGE_BUSYがあるバグはたったの3つで、
Bug 671894以外は役たたず。
ならば、と、ソースを検索したら、以下に遭遇。
ERROR_STORAGE_BUSYがあるソースSQLからSQLITE_BUSYが返ると、 convertResultCodeでNS_ERROR_STORAGE_BUSYを返していて、
BUSYだからBUSYでなくなるまでSleep、という、至極普通に思えるコードなのだが、
そこに、以下のコメントが...
コード:
148 // TODO (
Bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
149 // a busy error, so this handling can be removed.
150 nsresult rv = NS_OK;
151 do {
152 rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
153 if (rv == NS_ERROR_STORAGE_BUSY)
154 (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
155 } while (rv == NS_ERROR_STORAGE_BUSY);
Bug 1062823 を見ると、
以前のSQLiteが、ROLLBACKの時にペンディングリクエストがあると、SQLITE_BUSYを返して、後で出直してね、と言ってくるので、それでBUSYでなくなるまで待つ、ということのようで、
それが、SQLite 3.7.11以降は、ROLLBACKが、ペンディングリクエストがあった時、
ペンディングリクエストに対してはSQLITE_ABORT か SQLITE_ABORT_ROLLBACK error. を返してROLLBACKを実行するようになったから、
もう、BUSYでなくなるまで待つ必要はない、ということのようです。
これはROLLBACKの時だけの話で、この場合は、SQLITE_BUSYが返らなくなるまでリトライするから、
ROLLBACKでアプリ側にNS_ERROR_STORAGE_BUSYが伝わるのは、タイムアウトでキャンセルした時くらいしかない。
アプリがlocalStorage.setItem()を出すところでタイムアウト検出してどうこう、は、ちょっと考えられないから、
やっぱり、行ロックをとれなかった、ということの可能性が高いですね。
行ロックをとれなかった後、アプリ側は、何かのイベントの時にまたsetItem()を出しているだけだろうから、
NS_ERROR_STORAGE_BUSYが一度返ると消えない、というのは、
おそらく、localStorage内で、このアプリ用のscopeの行に関してデッドロックが発生している、ということなのでしょう。
タイミングホールだけでなく、アプリ側がclear()を多用していると、ROLLBACKとROLLBACKが衝突、というようなこともあるかもしれません。
極一部のアプリだけ、というのは、アプリ側が、こっちではKey1を更新してからKey2を更新、なのに、
あっちでは、Key2を更新してからKey1を更新、というようなことを、随所で行っている、
というようなことが関係してくるかもしれません。
元々がクッキーの置き換え・拡張だから、普通は、あるscopeにおいて一人だけが更新、だが、
アプリによっては、同じscopeで、複数のタスクが同時に更新、ということが起こるのかも知れません。
もしこういったことならば、原因は、localStorageがデッドロックを考慮していない、ということになるんだと思います。
なお、localStorageの中身を見たりするには、Firebug + FireStorage Plus!が便利そうです。
firefox-addon-to-view-edit-create-localstorage-data[追記]
既に、Toool/Web Developer/Storage Inspector が標準装備されてますね。
Storage Inspectorは、今のサイトのscopeのものについて、Cookie, IndexedDB, localStorage, sessionStorage, を見やすくして表示してくれて、親切。
Firebug + FireStorage Plus! のアドバンテージは、全部のscopeについて、一括で見られること。
[/追記]