C++ Signalhandler

Freezedevil

Lt. Junior Grade
Dabei seit
Mai 2011
Beiträge
507
Hi,

ich versuche gerade einen Signalhandler in eine Klasse zu stecken, da ich ein Attribut dieser Klasse brauche. Allerdings habe ich damit gewisse Schwierigkeiten.
Erstmal ein bisschen Code:

Code:
#include <iostream>
#include <signal.h>

using namespace std;

bool abort=false;

class Test {
private:
    int j;
public:
    Test (int i) : j(i) {}

    static void handler(int sig) {
        cout << endl << "closing" << endl;
        abort = true;
    }
};

int main() {
    cout << "started" << endl;
    Test t(3);
    signal(SIGINT, Test::handler);
    while(!abort){}
}
In diesem Minimalbeispiel möchte ich im Signalhandler gern die 3 ausgeben.
Auf j kann ich nicht zugreifen, da es nicht statisch ist. Ich kann es auch nicht statisch machen, da es erst beim erstellen des Objekts gesetzt werden kann.
Alternativ könnte ich die 3 in main beim Registrieren des Handlers übergeben, da die 3 dort bekannt ist. Soweit ich das aber überblicke ist das nicht möglich.

Fällt jemandem von euch eine elegante Lösung ein die ohne globale Variable auskommt? (Das globale abort wird es im fertigen Code auch nicht geben)

Und noch eine Frage. Ich habe irgendwo aufgeschnappt, dass es nicht gesund ist cout innerhalb eines Signalhandlers zu nutzen. Da war irgendwie von Deadlocks die Rede. Kann mich jemand dahingehend erleuchten, da ich bisher mit dem obigen Code keine Probleme hatte?

Ich danke euch schonmal und hoffe, dass ich ebenso aufschlussreiche Antworten wie sonst erhalte.
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Da die Signalhandler-Methode selbst static ist, kann sie natürlich nicht auf nicht-statische Member der Klasse zugreifen. Entweder du machst die benötigten Member-Variablen/Methoden static, oder du verschiebst deine Test-Instanz aus der main()-Funktion in den globalen Namespace(*). Solche C-Signalhandler sind meines Erachtens einer der wenigen Fälle, in denen globale Variablen gerechtfertigt sind.

EDIT: (*) Oder besser in einen anonymen Namespace auf Dateiebene.


Code:
#include <iostream>
#include <signal.h>
 
using namespace std;
 
bool abort=false;
 
class Test {
public:
    void handlerImpl( int sig )
	{
		// Nun kannst du normal auf 'j' zugreifen.
	}
private:
	int j;
	public:
	Test (int i) : j(i) {}
};

namespace
{
	Test t(3);
	
	void handler( int sig )
	{
		cout << endl << "closing" << endl;
		abort = true;
		
		t.handlerImpl( sig );
	}
}
 
int main() {
	cout << "started" << endl;
	signal(SIGINT, handler);
	while(!abort){}
}
 
Zuletzt bearbeitet:

Freezedevil

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Mai 2011
Beiträge
507
Aber die entscheidende Frage ist: Wozu brauchst du denn den Signalhandler überhaupt?
Um das Programm vernünftig zu beenden und zB geöffnete Sockets zu schließen. Wenn ich ein SIGINT nicht selbst behandle, werden meine Destruktoren schließlich nicht aufgerufen.


Da die Signalhandler-Methode selbst static ist, kann sie natürlich nicht auf nicht-statische Member der Klasse zugreifen.
Das hatte ich ich im ersten Post auch schon gesagt ;)

Dein Beispiel mit dem anonymen namespace gefällt mir allerdings gut. Ich werde es aber nicht zeitnah im realen Code testen können, da dort zuvor noch ein anderes Problem gelöst werden muss dessen Ursache mir noch nicht klar ist. Die Sache ist auch nicht sonderlich hoch priorisiert. Sollte es jedoch so weit kommen, werde ich diesen Ansatz sicher nutzen.

Vielen Dank.
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Und noch eine Frage. Ich habe irgendwo aufgeschnappt, dass es nicht gesund ist cout innerhalb eines Signalhandlers zu nutzen. Da war irgendwie von Deadlocks die Rede. Kann mich jemand dahingehend erleuchten, da ich bisher mit dem obigen Code keine Probleme hatte?
Hoppla, die Frage hatte ich total übersehen. Ja, es stimmt. In einem signal handler ist die Benutzung von iostreams nicht sicher. Das weiß ich aus eigener Erfahrung, da ich schon mit einem Programm zu kämpfen hatte, das in einem signal handler per cout eine Ausgabe tätigen wollte, was dazu führte, daß der ausführende Thread sich in der operator <<-Implementierung an irgend einem internen Mutex verhakte und somit das ganze Programm stehen blieb. Das liegt daran, daß die Implementierung der C++-Standard Library, die unser Compiler bereitstellte, zwar iostreams mitbrachte, die thread-safe nicht aber reentrant waren (Stack Overflow: Threadsafe vs re-entrant). Die Lösung war in unserem Fall, einfach auf printf() umzusteigen, das (zumindest auf unserem Compiler / OS) anscheinend diese Einschränkung nicht besaß.

Wenn du allerdings 100% standardkonformes C++ schreiben möchtest, kommt es sogar noch schlimmer. Im neuen Standard (C+11) ist auch ein threading-Modell enthalten. Deshalb könnte die Sache inziwschen anders aussehen (weiß ich nicht ... müßte man mal nachforschen), aber im Vorgängerstandard (C++03) war dies noch nicht der Fall, und deshalb führte die Benutzung von cout (und sogar printf()!!!) in signal handlers zu undefined behavior ... sprich, ob's funktioniert oder nicht, hängt strikt vom jeweiligen Compiler / OS ab.

Dazu mehr hier: Call only asynchronous-safe functions within signal handlers
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Nein, in diesem Fall bedeutet es einfach nur, daß der C++-Standard selbst hier keine Aussage macht. Dem Compilerhersteller selbst steht es aber durchaus frei, ein bestimmtes Verhalten festzulegen. Da man muß dann halt die Doku zum jeweiligen Compiler schmökern.
 

Freezedevil

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Mai 2011
Beiträge
507
Mit den Destruktoren hast du recht, aber die Frage ist: Will das der Nutzer überhaupt?
Aber sonst ist ein singleton, der am besten beim ersten Aufruf den signalhandler registriert und dann Buch führt über alle deine Objekte das wohl einfachste.
Da ich beim Testen regelmäßig warten muss bis ich wieder bind() auf einen Socket ausführen kann, nervt mich das in erster Linie selbst^^

Deinen Lösungsvorschlag interpretiere ich im wesentlichen so (ohne Maßnahmen die für ein Singleton nötig wären):

Code:
#include <iostream>
#include <signal.h>

using namespace std;

bool xyz = false;

class Test {
private:
        int var;

        static void handler(int sig) {
                cout << endl << "closing" << endl;
                xyz = true;
        }
public:
        Test(int i) : var(i) {
                signal(SIGINT, handler);
        }
};

int main() {
        Test t(3);
        while(!xyz) {}
        return 0;
}
Das funktioniert allerdings nicht und wirft folgenden Fehler
main.cpp:17: error: argument of type ‘void (Test::)(int)’ does not match ‘void (*)(int)’

Auch wenn ich den Fehler nicht 100%ig verstehe, so ist die Quintessenz doch, dass die Methode trotzdem static sein muss. Alles andere wäre auch nicht gesund wenn das Objekt welches die Methode enthält zerstört wird.

@antred Danke für die Erklärung. Ich verstehe deinen letzten Satz so, dass es entweder immer oder nie funktioniert (wenn Compiler und OS konstant sind). Kann man das so sagen oder ist es "Zufall" ob es sich aufhängt oder nicht?


@maxwell Wo findet man diese Formulierung? :)
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
@antred Danke für die Erklärung. Ich verstehe deinen letzten Satz so, dass es entweder immer oder nie funktioniert (wenn Compiler und OS konstant sind). Kann man das so sagen oder ist es "Zufall" ob es sich aufhängt oder nicht?
Wenn deine Compilerdoku explizit garantiert, daß printf() / cout reentrant sind, dann funktioniert es immer. Wenn sie dazu keine Äußerung macht, dann ist die Geschichte nach wie vor undefiniert, also ..

  • es könnte immer funktionieren, oder ...
  • es könnte überhaupt nie funktionieren, oder ...
  • es könnte einmal funktionieren und dann mal wieder nicht

Viel Spaß. ;)

P.S. und maxwell hat natürlich Recht, dein Rechner könnte in der Tat als 4. Möglichkeit auch eine Pizzabestellung auslösen. :D
 
Zuletzt bearbeitet:

Freezedevil

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Mai 2011
Beiträge
507
Alles klar. Dann hoffe ich mal letzteres, da ich langsam Hunger bekomme :D
 

Hancock

Captain
Dabei seit
Nov. 2007
Beiträge
3.375
Also deine Fehlermeldung: Einfach casten (signal(SIGINT,((void(*)(int))handler);).

Was ich meinte:
Code:
#include <set>
#include <vector>
class Object{
public:
    Object();
    virtual ~Object();
};
template<class T>class Singleton{
public:
    static T*getInstance(){
        if(!self.ptr)
            self.ptr=new T();
        return self.ptr;
    }
private:
    static class Inner{
    public:
        T*ptr;
        ~Inner(){
            if(ptr)
                delete ptr;
        }
    }self;
};
template<class T>typename Singleton<T>::Inner Singleton<T>::self;

//Test
void (*handler)(int);
class Sigint:public Singleton<Sigint>{
public:
    static void handle(Object*o){
        getInstance()->l.insert(o);
    }
    static void unhandle(Object*o){
        getInstance()->l.erase(o);
    }
    Sigint(){
        ::handler=handler;//signal(SIGINT,(void(*)(int))handler);
    }
private:
    set<Object*>l;
    static void handler(int){
        getInstance()->free();
    }
    void free(){
        vector<Object*>t(l.begin(),l.end());//Copy entire set, because the erase of unhandle would crash otherwise
        for(vector<Object*>::iterator i=t.begin();i!=t.end();++i)
            delete (*i);
    }
};
Object::Object(){
    Sigint::handle(this);
}
Object::~Object(){
    Sigint::unhandle(this);
}
class Test:Object{
    ~Test(){
        cout<<"Get deleted\n";
    }
};
void test(){
    cout<<(int)handler<<endl;
    new Test();
    cout<<(int)handler<<endl;
    handler(0);
    system("PAUSE");
}
Das wäre meine Implementation.
Vorteile:
Du überschreibst den Handler erst, wenn du es tatsächlich brauchst.
Du kannst mehr oder weniger RAII schreiben, wenn du von Object erbst (~Object wird aufgerufen, wenn du dein Programm beendest).
Nachteile: Du musst OO schreiben.
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Boah, tonnenweise boilerplate code, den's für so ein simples Thema eigentlich nicht braucht und dann auch noch ein Singleton (das wohl nutzloseste "Pattern" der Welt). Ich kann daran ehrlich gesagt überhaupt keine Vorteile entdecken.
 

Hancock

Captain
Dabei seit
Nov. 2007
Beiträge
3.375
Boilerplate: Klar man bekommt es auch kürzer, wenn man nur SIGINT überschreiben will...
Signal ist doch auch doof, wird irgendwann irgendwo aufgerufen, und das auch nur einmal pro Prozess. Ich würd sagen, das ist genau das, was ein Singleton auch ausmacht, das sind also "Seelenverwandte".
Vorteile: Du erbst von Object und musst dir keine Sorgen machen, dass es tatsächlich gelöscht wird, selbst bei einem SIGINT.
Da kann eine Socket-Klasse z.B. gekürzt so aussehen:
Code:
class Socket:public Object{
public:
	SOCKET s;
	Socket(){
		s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	}
	~Socket(){
		closesocket(s);
	}
};
Und in dem Fall wird auch bei einem SIGINT closesocket aufgerufen.
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Eine Singleton ist meines Erachtens nichts weiters als eine glorifizierte Globale. Also würde ich mir den ganzen Selbstbetrug sparen und einfach eine Globale nutzen. ;)
Und die Sicherheit, daß beim Beenden des Programms alles sauber aufgeräumt wird, bekommst du einfach, indem du (abgesehen von den Ausnahmen für das Signal-handling) keine globalen / statischen Variablen nutzt. Liegt alles auf dem Stack, wird's auch sauber abgeräumt, wenn der Stack abgerollt wird.
Obendrein sollte man in einem Signalhandler selbst so wenig wie möglich tun, eben weil es so viele Dinge gibt, die in einem Signalhandler eventuell nicht sicher sind. Und du scheinst ja zu suggerieren, es wäre am bequemsten, alle Aufräumarbeiten deines Programms in den Signalhandler zu verschieben.

Ich würde vorschlagen, den Signalhandler nichts anderes tun zu lassen, als die anderen Threads des Programms über dieses Ereignis zu informieren (da braucht's nicht viel mehr als eine std::condition_variable, und ein std::atomic oder was äquivalentes). Dann können die anderen Threads sich selbst beenden, und das Programm wird aufgeräumt, einfach weil wie schon erwähnt, alle auf den Stacks liegenden Objekte automatisch zerstört werden.
 

Hancock

Captain
Dabei seit
Nov. 2007
Beiträge
3.375
Klar ist ein Singleton eine glorifizierte Globale, aber inklusiv komischem Verhalten :) .

Die Sicherheit des Stacks geht aber nicht, weil bei einem Signal der Stack eben nicht aufgerollt wird. (So wie bei exit()).
Das Aufräumen sollte kein Problem sein, da im Dekonstruktor theoretisch alles, was ne Exception werfen kann, schon ein Problem ist.

@Vorschlag:
Wie willst du die Threads informieren. Du brauchst doch irgendeine Logik, die dazu führt, dass zeitnah der Thread sich selbst beendet (und Polling ist bei Deadlocks nicht möglich).
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Die Sicherheit des Stacks geht aber nicht, weil bei einem Signal der Stack eben nicht aufgerollt wird. (So wie bei exit()).
Das Aufräumen sollte kein Problem sein, da im Dekonstruktor theoretisch alles, was ne Exception werfen kann, schon ein Problem ist.
Ja, aber dafür implementierst du ja den Signalhandler, damit das Signal von dir behandelt wird und eben nicht einfach zum sofortigen Abbruch führt.

@Vorschlag:
Wie willst du die Threads informieren. Du brauchst doch irgendeine Logik, die dazu führt, dass zeitnah der Thread sich selbst beendet (und Polling ist bei Deadlocks nicht möglich).
Stimmt, aber diese Logik brauchst du in einem halbwegs komplexen Programm doch ohnehin. Dein Programm soll ja schließlich nicht bis in alle Ewigkeit laufen. In der Regel laufen die Threads des Programms doch sowie so in irgend einer Schleife. Und auch wenn der eigentliche Sinn von Threads ja ist, daß sie weitestgehend unabhängig voneinander und parallel zueinander ihre Arbeit erledigen, so müssen sie ja dennoch auch hin und wieder auf irgend eine Art und Weise miteinander kommunzieren. Warum also nicht einmal pro Durchlauf diesen Kommunikationsmechanismus nutzen, um zu prüfen, ob irgend eine Abbruchbedingung eingetreten ist, und dann die Threads ganz normal aus ihren Thread-routinen herausfallen lassen?
So können dann alle Aufräumarbeiten einfach über die Destruktoren der auf den Stacks liegenden Variablen ausgeführt werden.
 

Hancock

Captain
Dabei seit
Nov. 2007
Beiträge
3.375
Zeig mir mal, wie du in C++ den Stack nach einem Signal sauber aufgeräumt bekommst.

Zitat von TE:
Da ich beim Testen regelmäßig warten muss bis ich wieder bind() auf einen Socket ausführen kann, nervt mich das in erster Linie selbst^^
Daher denke ich, dass Deadlocks ein wesentlicher Teil des Problems sein könnten. Wozu bräuchte er sonst ein Signalhandler?
 

antred

Lt. Commander
Dabei seit
Juni 2010
Beiträge
1.288
Zeig mir mal, wie du in C++ den Stack nach einem Signal sauber aufgeräumt bekommst.
Wieso sollte das denn nicht gehen? Wir haben auf Arbeit mehrere CORBA-Server mit genau dem Signal-handing / threading-Konzept aufgezogen, das ich vorhin beschrieben hatte. Was speziell sind hier deine Bedenken?
Ergänzung ()

Also gut, hier mal ein stark vereinfachtes Beispiel (hier ohne sekundäre Threads).

Code:
#include <iostream>
#include <csignal>
#include <atomic>

namespace
{
	std::atomic< bool > gotTerminatingSignal = false;
}

void signalHandler( int sig )
{
	if ( SIGINT == sig || SIGTERM == sig )
	{
		gotTerminatingSignal = true;
	}
}

class Test
{
public:
	Test()
	{
		std::cout << "In c'tor of Test.\n";
	}

	~Test()
	{
		std::cout << "In d'tor of Test.\n";
	}
};

int main()
{
	std::signal( SIGTERM, signalHandler );
	std::signal( SIGINT, signalHandler );

	Test t;

	const std::size_t MAX_ITERATIONS = 10000000;
	const std::size_t DIVIDE = 50000;

	for ( std::size_t i = 0; i < MAX_ITERATIONS; ++i )
	{
		if ( gotTerminatingSignal )
		{
			std::cout << "Shutting down because terminating signal received.\n";
			return 0;
		}

		if ( i % DIVIDE == 0 )
		{
			std::cout << "Blah blah blah ...\n";
		}
	}
}
 
Top