バックグラウンド
マルチコンポーネントの C++ コードベースがあります。主要な実行可能ファイルを構成する 1 つの中心的なコンポーネントと、動的モジュール (.so ファイル) にコンパイルされる多数のコンポーネントがあります。中央の実行可能ファイルは、実行時にそれらをロードおよびアンロードできます (必要に応じてホットスワップします)。
Scheduler.h と呼ばれるファイルが 1 つあります。このファイルは、Scheduler
特定の時間または間隔で同期イベントを提供するクラスと、スケジューラへの要求に使用されるいくつかのヘルパー クラスを宣言します。タイミング データを保持するクラスと、単一の純粋仮想関数を持つEvent
抽象クラスがあります。また、Scheduler.h のほとんどの機能の定義を含む Scheduler.cpp もあります (ヘッダー ファイルで宣言および定義されるテンプレート クラスを除く)。action
DoEvent
はのEvent
サブクラスへのポインタを所有しておりaction
、これによってスケジューラの機能が制御されます。Scheduler.h は、これらのサブクラスのいくつかを提供します。
action
次のように宣言されています。
class action
{
action();
virtual ~action();
virtual DoEvent() = 0;
};
FunctionCallAction
のサブクラスはaction
、次のように宣言および定義されます。
template <class R, class T>
class FunctionCallAction : public action
{
public:
FunctionCallAction(R (*f)(T), T arg) : argument(arg), callback(f) {}
~FunctionCallAction() {}
void DoEvent() { function(argument); }
private:
R (*callback)(T);
T argument;
};
HelloAction
、別のサブクラスは、次のように宣言されています。
// In Scheduler.h
class HelloAction : public action
{
~HelloAction();
void DoEvent();
};
// in Scheduler.cpp
HelloAction::~HelloAction() {}
void HelloAction::DoEvent() { cout << "Hello world" << endl; }
私の動的ライブラリの 1 つは、CloneWatch
CloneWatch.h で宣言され、CloneWatch.cpp で定義されており、このスケジューラ サービスを使用しています。コンストラクターで、300 秒ごとに実行されるようにスケジュールされた永続的なイベントを作成します。デストラクタで、このイベントを削除します。このモジュールが読み込まれると、既存のスケジューラ オブジェクトへの参照が取得されます。モジュールを「ロード」するプロセスには、 を使用dlopen()
してライブラリを開き、dlsym()
ファクトリ メソッド (適切な名前の ) を検索し、Factory
このファクトリ メソッドを使用してオブジェクトのインスタンスを作成する必要があります (セマンティクスは関係ありません)。ライブラリを閉じるには、ファクトリ メソッドによって作成されたオブジェクトが削除されdlclose()
、プロセスのアドレス空間からライブラリを削除するために呼び出されます。
実行時のライブラリのロードとアンロードは、コマンドによって制御されます。
// relevant declarations
const float DB_CLEAN_FREQ = 300;
event_t cleanerevent; // event_t is a typedef to an integral type
void * RunDBCleaner(void *); // static function of CloneWatch
Scheduler& scheduler;
// in constructor:
Event e(DB_CLEAN_FREQ, -1, new FunctionCallAction<void *, void *>(CloneWatch::RunDBCleaner, (void *) this));
cleanerevent = scheduler.AddEvent(e);
// in destructor:
scheduler.RemoveEvent(cleanerevent);
Scheduler::RemoveEvent
怠惰です。イベントの優先キュー全体をトラバースするのではなく、「キャンセルされたイベント」のセットを維持します。イベント処理中に、キャンセルされたイベントのセット内の ID と一致する ID を持つイベントがキューからポップされた場合、そのイベントは実行または再スケジュールされず、すぐにクリーンアップされます。イベントをクリーンアップするプロセスでは、イベントがaction
所有するオブジェクトを削除する必要があります。
問題
私が抱えている問題は、プログラム セグメントに障害があることです。障害は、スケジューラのイベント ループ内で発生します。これは、大まかに次のようになります。
while (!eventqueue.empty() && e.Due())
{
Event e = eventqueue.top();
eventqueue.pop();
if (cancelled.find(e.GetID()) != cancelled.end())
{
cancelled.erase(e.GetID());
e.Cancel();
continue;
}
QueueUnlock();
e.DoEvent();
QueueLock();
e.Next();
if (e.ShouldReschedule()) eventqueue.push(e);
}
への呼び出しはe.Cancel
、イベントのアクションを削除します。を呼び出すと、イベントのアクションe.Next
が削除される場合があります (イベントが勝手に期限切れになった場合のみ。この場合、e.ShouldReschedule は false を返し、イベントは破棄されます)。テスト目的で、アクション クラスとサブクラスのデストラクタにいくつかの print ステートメントを追加して、何が起こっているかを確認しました。
キッカー
イベントが から削除された場合、e.Next
期限切れになるまで、すべてが通常どおり進行します。ただし、モジュールをアンロードすると、キャンセルされたリストを通じてイベントがリタイアされ、アクションのデストラクタが呼び出されるとすぐに、プログラムでセグメンテーション エラーが発生します。これは、スケジューラがイベントを遅延削除するため、モジュールがアンロードされた後に発生します。
デストラクタには入りませんが、すぐに失敗します。イベントのアクションの管理された削除と管理されていない削除のさまざまな組み合わせを試し、さまざまな場所や方法でそれを実行しました。私はそれを valgrind と gdb で実行しましたが、どちらもセグメンテーション違反が発生したことを丁寧に通知してくれました。私の人生では、理由を特定することはできません (ただし、どちらの使い方もよくわかりません)。 .
ループの本体も呼び出しe.Cancel
て削除を強制し、イベントを再スケジュールする行をコメントアウトして、イベントが実行されるとすぐにキャンセルされるようにすると、障害は発生しません。
アクションも に置き換えましたHelloAction
が、これは問題ありません。のデストラクタについて非常に具体的なことはFunctionCallAction
、明らかに問題がある場所です。セマンティック エラーはほぼ解消されましたが、これはコンパイラまたは動的リンカのあいまいな動作の結果であると思われます。誰かが問題を見ていますか?