Thread CLONE_SKIP 'verteufelt' Referenzen auf Objekte - wie vermeiden? (3 answers)
Opened by Bernd at 2013-01-01 10:59

Gast Bernd
 2013-01-01 10:59
#164569 #164569
Erst einmal ein frohes neues Jahr an alle.
Ich schreibe derzeit einen IRC-Bot, der sich in einen Kanal einloggen und dann eben alle möglichen Funktionen ausführen soll. Hierzu habe ich eine Klasse IRC geschrieben und habe eine open-Funktion, die den Socket erstellt, testet und zur Verfügung stellt.

Das Problem bei IRC ist, dass die Transaktionen asynchron durchgeführt werden können, während ich also in den Socket schreibe, kann der Server mir bereits Daten zugesendet haben. Daran ist nichts zu machen, aber die Daten, die der Server mir zusendet, lassen sich puffern, bis sie gebraucht werden, der Bot holt sich dann nach einer Transaktion oder in der Ausführungsschleife einfach die Daten und verarbeitet diese. Leider hatte ich im Vorfelf einige Probleme mit allen Funktionen (read, readline, revc und sysread), die Daten aus einem Socket lesen sollen, wenn dort keine Daten mehr vorhanden sind, die jeweilige Funktion wartete dann immer, BIS wieder Daten vorhanden sind oder der Socket geschlossen wurde. Den Socket als nichtblockierend zu markieren hat in enormer Laufzeitverschlechterung resultiert, da die Daten nun byte-, nicht block/zeilenweise in den Puffer geschrieben wurden.

Nachdem ich das Problem einem Kollegen schilderte, schlug dieser vor, Multithreading zu verwenden: Innerhalb von open sollte ein zweiter Thread erstellt werden, der nur damit beschäftigt ist, eingehende Daten in den Puffer zu schreiben. Eine Zugrifffunktion in der Klasse soll die Daten bei Aufruf zurückgeben und den Puffer leeren. Daten in den Socket zu schreiben sollte nur vom Hauptthread übernommen werden.

Allerdings erhielt ich nach dem Schreiben und Einbinden der Funktion run, welches permanent Daten vom Socket lesen sollte, beim ersten Versuch die Fehlermeldung, dass ein nicht erlaubter Seek durchgeführt wurde. Nach etlichem Debuggen stellte sich heraus, dass das Paket threads und die Funktion create, mit der ich den Thread erstelle, einfach eine eigene Kopie der Elemente der Referenz anlegt und der Funktion als Parameter übergibt. Zur Erklärung, ich komme ursprünglich aus der C++-Sparte und dachte, wenn man eine Referenz auf ein Objekt übergibt, wird der Kopierkonstruktor nicht aufgerufen. Der Thread soll in Echtzeit auf bestimmte Elemente des IRC-Objektes zugreifen können, weshalb ich eine Referenz und kein Objekt, welches kopiert werden könnte, übergab.

Nach kurzer Recherche fand ich heraus, dass mit dem Überladen der CLONE_SKIP-Funktion in der Klasse dieses Verhalten unterbunden werden kann. Das hat auch prima funktioniert, es wird nun keine Kopie des eigentlichen Objektes mehr erstellt. Allerdings verwirft threads nun sämtliche Unterobjekte/Referenzen/Adressen, die im IRC-Objekt definiert sind, das blessing wird vollkommen ignoriert, und ich habe keine Möglichkeit, auf den Socket zuzugreifen.

Etwas Code zum Direktkompilieren:

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/perl -w
use strict;

package IRC;
use POSIX qw(:errno_h);
use IO::Socket;
use threads ('yield','stack_size'=>16384,'exit'=>'threads_only','stringify');
use threads::shared;

sub CLONE_SKIP{1}

#Neues IRC-Objekt
sub new($$$)
{
my $class =shift or return;
my $self ={};
my $sock_opt =shift;
my $irc_opt =shift;

bless($self,$class);

$self->{'sock_opt'} =$sock_opt if($sock_opt && ref($sock_opt) eq 'HASH');
$self->{'irc_opt'} =$irc_opt if($irc_opt && ref($irc_opt) eq 'HASH');

return $self;
}

#IRC-Verbindung oeffnen.
sub open($$$)
{
my $self =shift or return;
my $sock_opt =shift or undef;
my $irc_opt =shift or undef;

$self->{'sock_opt'} =$sock_opt if($sock_opt);
$self->{'irc_opt'} =$irc_opt if($irc_opt);

$self->{'socket'}=IO::Socket::INET->new(%{$self->{'sock_opt'}});
$self->{'socket_input'}='';

#XXX: Hier ist $self noch IRC=HASH ...
print "1: Hauptthread, die Welt ist in Ordnung:\n";
print "1: \$self und \$self->{'socket'}\n";
print "1: $self und $self->{'socket'}\n";
my $thr=threads->create('run',$self) or die "1: $!\n";
$thr->detach();# or die "2: $!\n";
}

#Daten vom Socket erhalten
sub get_response($)
{
my $self=shift or return '';
my $tmp=$self->{'socket_input'};
$self->{'socket_input'}='';
return $tmp;
}

sub run($)
{
my $self=$_[0] or return;

#XXX: Hier nicht mehr:
print "2: Unterthread zum Einlesen von Socketdaten, aber ich habe mein Blessing verloren ...\n";
print "2: \$self\n";
print "2: $self\n";

#Diese Anweisung fuehrt dazu, dass der Thread unweigerlich kaputt geht, weil $self sein Blessing verloren hat.
print "Hier stirbt der Thread immer, da \$self->{'socket'} nicht mehr existiert\n";
my $socket=$self->{'socket'} or return;

#Normalerweise sonst aus dem Socket lesen.
my $input;
while($input=<$socket>)
{
$input=~s#\r##sg;
$self->{'socket_input'}.=$input;
}
}

#Nun Wechsel zum normalen Programm.
package main;

#Neuen IRC-Handler erstellen, Verbindung aufbauen und Antwort (in der Regel sowas wie 'Verifiziere den Hostnamen' oder in der Art) erhalten und ausgeben.
my $irc_handler=IRC->new();
$irc_handler->open({'PeerAddr'=>'gibtesnicht.net','PeerPort'=>6667,'Proto'=>'tcp'},{'IRCNick'=>'Bernd','IRCOwners'=>'Bernd','IRCChannels'=>'#myownchannel'});
print $irc_handler->get_response()."\n";


Ich vermute inzwischen, dass ich einen Designfehler habe - könnte mir jemand einen Anstoß geben, wie das Problem besser zu lösen ist, oder zumindest das Unblessing verhindert?
Last edited: 2013-01-01 11:13:59 +0100 (CET)

View full thread CLONE_SKIP 'verteufelt' Referenzen auf Objekte - wie vermeiden?