実際のセットアップ、その背後にある考え方、何が壊れているのか、何を試したのかを説明しようと思います。
コンテキスト
「イベント」(いくつかのデータを含む標準配列であり、その中には一意の連続番号が含まれるイベント)をJavascript(jQuery 1.7.xを使用)にフィードするPHP5.3バックエンドがあります。イベントは、jsonp(サブドメイン上)とサーバー側のロングポーリングを使用して取得されます。最初のイベントのIDは1で、新しいイベントごとに増分します。クライアントは「最後に取得されたイベントID」を追跡し、その値は0から始まります。ロングポーリング要求ごとにそのIDが提供されるため、バックエンドはその後に発生したイベントのみを返します。
イベントは次の方法で処理されます。(jsonpコールバックを介して)受信されると、イベントはeventQueue変数に格納され、「最後に取得されたイベントID」は、受信されてキューに格納された最後のイベントの1つに更新されます。次に、キューに入れられた次のイベントを処理する関数が呼び出されます。その関数は、イベントがすでに処理されているかどうかをチェックし(イベントが処理され始めるたびに設定される別の変数を使用して)、何も行わない場合は、コールスタックがjsonpコールバックに戻ります。ロングポーリング要求が発行されます。(これにより、他のイベントが処理されている間、新しいイベントをキューに入れるプロセスが繰り返されます)ただし、現在処理されているイベントがない場合は、キューにイベントが残っているかどうかを確認します。その場合、最初のもの(IDが最も小さいもの)を処理します。「イベントの処理」は、私のアプリケーションに関連するさまざまなタスクですが、私が抱えている問題やコンテキストには関係しません。たとえば、変数やページ上のメッセージなどを更新します。イベントが「処理中」と見なされると(一部のイベントはデータを取得または送信するためにajax呼び出しを行います。この場合、これは成功したajaxコールバックで発生します)。 eventCompleteと呼ばれる別の関数が呼び出されます。この関数は、処理されたイベントをイベントキューから削除し、イベントが処理されているかどうかを処理する変数がfalseに設定されていることを確認してから、イベントキューを処理する関数を呼び出します。(したがって、次の最小IDのイベントを処理します)しかし、私が抱えている問題や文脈には関係ありません。たとえば、変数やページ上のメッセージなどを更新します。イベントが「処理中」と見なされると(一部のイベントはデータを取得または送信するためにajax呼び出しを行います。この場合、これは成功したajaxコールバックで発生します)。 eventCompleteと呼ばれる別の関数が呼び出されます。この関数は、処理されたイベントをイベントキューから削除し、イベントが処理されているかどうかを処理する変数がfalseに設定されていることを確認してから、イベントキューを処理する関数を呼び出します。(したがって、次の最小IDのイベントを処理します)しかし、私が抱えている問題や文脈には関係ありません。たとえば、変数やページ上のメッセージなどを更新します。イベントが「処理中」と見なされると(一部のイベントはデータを取得または送信するためにajax呼び出しを行います。この場合、これは成功したajaxコールバックで発生します)。 eventCompleteと呼ばれる別の関数が呼び出されます。この関数は、処理されたイベントをイベントキューから削除し、イベントが処理されているかどうかを処理する変数がfalseに設定されていることを確認してから、イベントキューを処理する関数を呼び出します。(したがって、次の最小IDのイベントを処理します)eventCompleteと呼ばれる別の関数が呼び出されます。この関数は、処理されたイベントをイベントキューから削除し、イベントが処理されているかどうかを処理する変数がfalseに設定されていることを確認してから、イベントキューを処理する関数を呼び出します。(したがって、次の最小IDのイベントを処理します)eventCompleteと呼ばれる別の関数が呼び出されます。この関数は、処理されたイベントをイベントキューから削除し、イベントが処理されているかどうかを処理する変数がfalseに設定されていることを確認してから、イベントキューを処理する関数を呼び出します。(したがって、次の最小IDのイベントを処理します)
問題
これは、テストされたすべての主要なブラウザーでも非常にうまく機能します。(Internet Explorer 8および9、Chrome、Opera、Firefoxでテスト済み)また、長いポーリングを利用しているため、非常に高速です。また、ページをリロードした後でも、発生したことのすべての「履歴」(ほとんどのイベントはページ内の一種のコンソールに追加されるテキストデータを生成します)を取得し、アプリケーションのまったく同じ状態になるのは本当に素晴らしいことです。ただし、これはイベントの数が多くなると問題になります。見積もりに基づくと、30,000ものイベントを処理できる必要があります。私のテストでは、7,000のイベントでさえ、物事はうまくいかなくなり始めています。InternetExplorer8スタックは約400のイベントをオーバーフローします。Chromeはすべてのイベントを読み込むわけではありませんが、近づきます(ただし、IE8とは異なり、常に同じ時点で中断するわけではありません)。IE9とFFはすべてをうまく処理し、すべてのイベントが処理されている間、2〜3秒ハングします。これは許容範囲です。しかし、それはまた、それらが壊れる前に、いくつかのより多くのイベントの問題であるかもしれないと私は考えています。私は現在のWebブラウザーを要求しすぎているのでしょうか、それとも何か間違っているのでしょうか。それを回避する方法はありますか?私のモデル全体が間違っていますか?
可能な解決策
私はいくつかのアイデアをいじりましたが、どれも実際には機能しませんでした。バックエンドに一度に200を超えるイベントを出力しないように強制し、現在のすべてのキューの処理が完了した後に新しいポーリング要求を追加しようとしました。まだスタックオーバーフローが発生しています。また、処理が完了した後にオブジェクトを削除してeventQueue
(その時点では空ですが)再作成してみました。これにより、基になるメモリなどが解放される可能性があります。私はアイデアが不足しているので、アイデア、ポインタ、または一般的なアドバイスをいただければ幸いです。
編集:
悟りがありました!私はこれらすべてが起こっている理由を正確に知っていると思います(しかし、それにアプローチして修正する方法はまだわかりません)、いくつかの基本的なコードの抜粋も提供します。
var eventQueue = new Object();
var processingEvent = false;
var lastRetrievedEventId = 0;
var currentEventId = 0;
function sendPoll() {
// Standard jsonp request (to a intentionally slow backend, i.e. long-polling),
// callback set to pollCallback(). Provide currentEventId to the server to only get
// the events starting from that point.
}
function pollCallback( data ) {
// Make sure the data isn't empty, this happens if the jsonp request
// expires (30s in my case) and it didn't get any new data.
if( !jQuery.isEmptyObject( data ) )
{
// Add each new event to the event queue.
$.each(data.events, function() {
eventQueue[ this.id ] = this;
lastRetrievedEventId = this.id; // Since we just put the event in the queue, we know it is officially the last one "retrieved".
});
// Process the next event, we know there has to be at least one in queue!
processNextEvent();
}
// Go look for new events!
sendPoll();
}
function processNextEvent() {
// Do not process events if they are currently being processed, that would happen
// when an event contains an asynchronous function, like an AJAX call.
if( !processingEvent )
{
var nextEventId = currentEventId + 1;
// Before accessing it directly, make sure the "next event" is in the queue.
if( Object.prototype.hasOwnProperty.call(eventQueue, nextEventId) )
{
processingEvent = true;
processEvent( eventQueue[ nextEventId ] );
}
}
}
function processEvent( event ) {
// Do different actions based on the event type.
switch( event.eventType ) {
case SOME_TYPE:
// Do stuff pertaining to SOME_TYPE.
eventComplete( event );
break;
case SOME_OTHER_TYPE:
// Do stuff pertaining to SOME_OTHER_TYPE.
eventComplete( event );
break;
// Etc. Many more cases here. If there is an AJAX call,
// the eventComplete( event ) is placed in the success: callback
// of that AJAX call, I do not want events to be processed in the wrong order.
}
}
function eventComplete( event ) {
// The event has completed, time to process the event after it.
currentEventId = event.id; // Since it was fully processed, it is now the most current event.
delete eventQueue[ event.id ]; // It was fully processed, we don't need it anymore.
processingEvent = false;
processNextEvent(); // Process the next event in queue. Most likely the source of all my woes.
}
function myApplicationIsReady() {
// The DOM is fully loaded, my application has initiated all its data and variables,
// start the long polling.
sendPoll();
}
$(function() {
// Initializing my application.
myApplicationIsReady();
});
物事を見て、コールスタックが多くのイベントでいっぱいになる理由を理解しました。例(->は呼び出しを意味します):
myApplicationIsReady() -> sendPoll()
そして、データを取得するとき:
pollCallback() -> [ processNextEvent() -> processEvent() -> eventComplete() -> processNextEvent() ]
括弧内の部分は、ループしてコールスタックオーバーフローを引き起こす部分です。少量のイベントでは発生しません。これは、次のためです。
pollCallback() -> processNextEvent() -> processEvent() -> eventComplete() -> sendPoll()
これは2つのイベントで、最初のイベントには非同期呼び出しが含まれます。(つまり、最初のイベントが処理されていないために処理されない2番目のイベントに到達します。代わりに、ポーリング関数を呼び出します。これにより、コールスタック全体が解放され、最終的にはそこからのコールバックによってアクティビティが再開されます)
今では修正するのは簡単ではなく、そもそもそのように設計されています。理由は次のとおりです。
- イベントを失いたくない(のように、すべてのイベントが処理されていることを確認したい)。
- ブラウザをハングさせたくありません(同期AJAX呼び出しまたは何かが終了するのを待つ空のループを使用できません)。
- 私は絶対にイベントが正しい順序で処理されることを望んでいます。
- イベントがキューでスタックし、アプリケーションがそれらを処理しなくなることを望んでいません。
それは私が今助けを必要としているところです!私が望むことを行うには、チェーンを使用する必要があるように聞こえますが、それがまさに私のコールスタックの問題を引き起こしているものです。おそらく、コールスタックの奥深くに行くことなく、すべてを実行できるより優れたチェーン構造があり、それを見落としていた可能性があります。よろしくお願いします。進歩している気がします!