2009-07-04 6 views
1

Ich schreibe eine kleine Netzwerk-Anwendung in Cocoa mit objective-c 2.0. Ich habe den Garbage Collector im erforderlichen Modus aktiviert (-fobjc-gc-only). Wenn ich den Code die meiste Zeit benutze, funktioniert das wie ein Zauber. Aber manchmal stürzt es einfach ohne Warnung ab und gdb startet. Ich kann noch keine nützlichen Informationen von GDB erhalten. Der Code lautet wie folgt:Cocoa-Netzwerk-Streams und der Garbage Collector

NSHost *host = [NSHost hostWithName:@"hostname"]; 
    [NSStream getStreamsToHost:host port:1234 
        inputStream:&iStream outputStream:&oStream]; 

    if (iStream == nil || oStream == nil) { 
     NSLog(@"Unable to open streams."); 
     return; 
    } 

    [iStream setDelegate:self]; 
    [oStream setDelegate:self]; 

    [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

    [iStream open]; 
    [oStream open]; 
} 

    - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { 
     NSLog(@"Event: '%d'", eventCode); 
    } 

kann ich diesen Code ein paar Mal ohne die Anwendung abstürzt ausführen, aber irgendwann wird es zum Absturz bringen. GDB gibt keine nützlichen Informationen, soweit ich das beurteilen kann, aber ein stacktrace könnte nützlich sein:

#0 0x9438d688 in objc_msgSend() 
#1 0x95fe3451 in _inputStreamCallbackFunc() 
#2 0x9561c549 in _CFStreamSignalEventSynch() 
#3 0x9561e117 in CFReadStreamSignalEvent() 
#4 0x90c7702f in SocketStream::socketCallback() 
#5 0x90c77153 in SocketStream::_SocketCallBack_stream() 
#6 0x956137bb in __CFSocketDoCallback() 
#7 0x95614f05 in __CFSocketPerformV0() 
#8 0x9560a595 in CFRunLoopRunSpecific() 
#9 0x9560ac78 in CFRunLoopRunInMode() 
#10 0x90e5028c in RunCurrentEventLoopInMode() 
#11 0x90e4ffde in ReceiveNextEventCommon() 
#12 0x90e4ff19 in BlockUntilNextEventMatchingListInMode() 
#13 0x914add0d in _DPSNextEvent() 
#14 0x914ad5c0 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]() 
#15 0x914a65fb in -[NSApplication run]() 
#16 0x91473834 in NSApplicationMain() 
#17 0x00001f90 in main (argc=1, argv=0xbffff758) at /Users/blubber/Documents/MacLight/main.m:13 

ich nicht herausfinden kann, was hier schief geht. Obwohl es scheint, dass irgendein Objekt (vielleicht einer der Ströme) durch den GC vorzeitig gelöscht wird.

+0

Verwenden Sie NSZombieEnabled. http://developer.apple.com/technotes/tn2004/tn2124.html –

+0

Probieren Sie es aus, es gibt keinerlei Informationen. Danke trotzdem. –

+0

Es nicht? Sie erhalten keine Meldung "Nachricht an freigegebene Instanz gesendet"? –

Antwort

0

Dies ist nichts anderes als eine wilde Vermutung, aber versuchen Sie Ändern/Hinzufügen dieses:

[iStream open]; 
    [oStream open]; 
    CFRetain(self); 
} 

Dies ist nur für Testzwecke ist, und Sie werden wahrscheinlich ‚Leck‘ self als Ergebnis. Ich begründe diese Empfehlung, weil die Stack-Trace während irgendeiner Art von Callback-Verarbeitung abstürzt, und Sie sich selbst als den Delegaten festgelegt haben, so dass das Callback-Ziel Sie sind.

Wenn das Problem "behebt", dann habe ich keine Ahnung, was das Problem ist (seltsam wie es klingt).

BEGIN EDIT

Würden Sie es nicht wissen, gleich nachdem ich die Antwort geschrieben, ich glaube, ich habe das Problem gefunden. Nehmen Sie die folgende Änderung (mit Angabe der CFRetain Bit nicht hinzugefügt werden):

[NSStream getStreamsToHost:host port:1234 inputStream:&iStream outputStream:&oStream]; 
iStream = iStream; 
oStream = oStream; 

WICHTIG: beide iStream und oStream müssen ivars, Teil Ihrer Klassendeklaration sein. Da das Code-Snippet, das du gepostet hast, dies als eine Möglichkeit offen lässt, dachte ich nur, dass es klar wäre.

Wenn sein die Fehler, den ich denke, es ist, ich sei bereit, diese ‚Korrekturen‘ zu, das Problem zu setzen:

iStream = self; 
oStream = self; 
[NSStream getStreamsToHost:host port:1234 inputStream:&iStream outputStream:&oStream]; 

Vorausgesetzt, das ist, was ich denke, es ist, sollten Sie in der Lage sein, zu entfernen die Korrekturen, fügen Sie etwas wie NSLog(@"self is: %p, iStream is: %p, oStream is: %p", self, iStream, oStream) hinzu und warten Sie dann, bis es abstürzt. Dann wird in den Debugger, geben Sie so etwas wie:

gdb> p *(MySelfClass *)0xdeadbeef 

Wo MySelfClass ist Ihr Klassennamen und 0xdeadbeef ist, was auch immer der Zeiger für self ist. Die Werte für iStream und oStream sollten identisch sein mit NSLog Ausspucken. Geben Sie dann in

gdb> info gc-references 0xPTR 
gdb> info gc-roots 0xPTR 

Für jede self, iStream, oStream. Ich denke, Sie werden feststellen, dass das einzige System, das dem GC-System bekannt ist, self ist, trotz der Tatsache, dass Ihre Ivars Zeiger haben.

Ein weiteres extrem frustrierendes Problem, das ich bei der Verwendung von Cocoa GC-System hatte, ist der Compiler erzeugt fehlerhaften Code für __strong Zeiger (Ich habe mehrere Bugs in diesem Zusammenhang eingereicht). Meistens verursacht dies keinen Absturz, da das Problem durch einen anderen Live-Zeiger maskiert wird, was durch die Tatsache verschlimmert wird, dass beide Zeiger in 99% der Fälle dazu neigen, nahezu gleichzeitig zu "sterben". Das GC-System führt nur einige Male pro Sekunde eine Sammlung durch, aber meistens weniger häufig.

Dann gibt es die Zeiten, wo es keinen anderen Zeiger gibt, um den Compilerfehler zu maskieren. Dies ist ein perfektes Beispiel: Sie benötigen diese Zeiger, um mehrere Durchlauf-Schleifeniterationen zu überstehen. In diesen Fällen werden Sie schließlich irgendwann mit einem Sammlungslauf über die Pfade gehen. Wenn das passiert, ist es völlig zufällig, es könnte mehrmals pro Sekunde oder einmal pro Minute sein.

END EDIT

Es gibt eine zweite 'Lösung' für Ihr Problem: Verwenden GC nicht. In der Tat, ich empfehle dies dringend, und das kommt aus der praktischen Erfahrung mit diesem GC-System seit 10.5 Beta in der realen Welt, nicht-triviale Programme. Ein Grund, warum ich seine Verwendung ablehne, ist rein pragmatisch: Während das Debugging von manueller Speicherverwaltung/Referenz-Zählung schwierig sein kann und Zeitverschwendung ist, ist es praktisch unmöglich, GC-Probleme zu debuggen. Ich würde jeden Geldbetrag wetten, auf den dein Problem hinausläuft: Es ist im Wesentlichen ein Multithread-Race-Condition-Bug. Dies ist die am schwierigsten zu debuggende Klasse von Problemen.

Meine Vermutung ist, wo einige, etwas, was die __strong Qualifier auf einen GC verwalteten Zeiger gesunken ist, und der größte Täter ist ein NSObject *object; zu einem void *pointer;, wie pointer = object;. Der Compiler lässt Sie dies ohne eine Warnung tun, obwohl es zu den Arten von Problemen führt, die Sie haben, und ist fast immer ein Fehler, oder öffnet das Potenzial für diese Art von Fehlern. Manchmal ist es nicht einmal offensichtlich, dass Sie dies getan haben, z. B. wenn Sie ein Objekt an ein Argument übergeben, das den Typ void * annimmt. Am schlimmsten ist, dass Sie im letzten Fall nichts mehr tun können: Die Methode oder Funktion wurde nicht so kompiliert, dass der empfangene Zeiger als __strong behandelt wird.

Es gibt keine generische Methode, wie Sie diese Fälle korrekt behandeln können, damit sich Ihr Programm deterministisch verhält und nicht abstürzt. Es könnte buchstäblich nicht einmal ein Weg sein. Wenn Sie unweigerlich auf einen dieser Fälle stoßen, die viel häufiger sind, als Sie denken, oder das Problem selbst vergessen, indem Sie __strong vergessen, verspreche ich Ihnen, dass alle "Gewinne" in der Produktivität, die Ihnen mit GC versprochen wurden, gelöscht werden hinaus, zu dem Punkt, wo GC nicht verwendet worden wäre, wäre sowohl viel schneller als auch viel einfacher gewesen.

Das nächste Problem, auf das ich stieß, sind Leistungsprobleme. Im Bereich von zwei bis fünf mal langsamer. Dies liegt daran, dass dem GC-System mitgeteilt werden muss, wenn ein GC-Manager-Zeiger irgendwo auf dem Heap gespeichert ist. Dies ist normalerweise nur ein einzelner Befehl, aber der Compiler schreibt die Zuweisung erneut in einen Funktionsaufruf objc_assign_strongCast() (!), Der immer eine globale GC-Mutex-Sperre (!!) annimmt. Dies ist eine schockierend teure Operation.

Dies ist eine ziemlich häretische Empfehlung und ich bin mir ziemlich sicher, dass dies Cocoa GC Apologeten in Scharen hervorbringen wird.Die Argumente im Grunde laufen auf:

Apples testing showed that for real world applications there was no performance impact.

Dies ist keine Verteidigung ist, und es kommt fast immer von einem Apple-Mitarbeiter. In keiner Weise adressiert, negiert oder behebt dieser Anspruch die Leistungsprobleme meiner real world app. Und nach akademischen Maßstäben kann dieser Anspruch nicht einmal ernst genommen werden, es sei denn, Sie sind bereit, die Daten und Methoden zur Verfügung zu stellen, mit denen ich die gleichen Ergebnisse überprüfen und reproduzieren kann.

Lots of real world applications use GC.... like Xcode!

In aller Ehrlichkeit, haben die einzige Anwendung, die ich je ist Xcode erwähnt gesehen. Eines Tages (vor nicht allzu langer Zeit) habe ich mir die Mühe gemacht, den ganzen GC mit Apps auf meinem Rechner zu finden. Es gab zwei Apps: Xcode.app und /Developer/Applications/Utilities/Property List Editor.app/. Dies beinhaltet alle Apps von Drittanbietern, die ich hatte, zusammen mit den Entwicklertools (natürlich), Standard OS X-Sachen, iLife und iWork '09. Abgesehen von der Tatsache, dass es sich hierbei nicht um ein korrektes Beispiel für die Verwendung von GC durch Dritte handelt, gibt es etwas sehr Interessantes, das zunächst nicht offensichtlich ist: Es gibt sehr viele Apple-Anwendungen, die dies abdecken, und es gibt genau eine real world app : Xcode.app.

Dies wird umso interessanter durch die Tatsache, dass Apple anscheinend in Haustests durchgeführt hat, um GC Auswirkungen auf die Leistung zu bestimmen. Ich denke, es ist sicher zu sagen, dass die Apps, die abgedeckt werden, einige der höchsten auf der Liste sein werden, um zu sehen, ob GC irgendwelche Auswirkungen auf die Apps hat. Dies bedeutet, dass entweder Apple die Konvertierung einiger dieser Apps in GC vorgenommen hat und dann entschieden hat, die GC-Version nicht zu versenden (was sofort die Frage aufwirft, warum nicht), oder dass es berechtigte Zweifel darüber aufkommen lässt, wie viele reale Welten es gibt Apps wurden verwendet.

0

"Es gibt eine zweite" Lösung "für Ihr Problem: Verwenden Sie nicht GC. In der Tat, ich empfehle dringend, und dies kommt aus der praktischen Erfahrung mit diesem GC-System seit 10.5 Beta in der realen Welt, nicht triviale Programme. "

Ich muss es bestätigen. Ich hatte eine negative Erfahrung mit GC in ziemlich großen kommerziellen Produkt, GC-Anwendung wurde rund 180 MB pro Stunde. Ich konnte mit Instrumenten nichts anfangen - es zeigte echten "Müll", das gleiche war GDB-Ausgabe. GC-Apps sind undicht, viele Porrees befinden sich in Kakao-Interna (ich glaube, dass Apple GC in den meisten eigenen Produkten nicht verwendet, daher wurde es nicht wirklich gründlich getestet).

Das andere Problem ist die Leistung. GC erhöht die CPU-Last signifikant, ich habe nach der Aktivierung von GC einen 100% igen Anstieg erfahren und ich habe periodisch ein "Regenbogenrad" für einige Sekunden erlebt.

Jetzt portierte ich die Anwendung auf "manuellen Modus", löste Lecks und bekam eine viel bessere CPU-Auslastung.

Garbage Collector ist sicherlich eine brillante Sache, aber die Implementierung ist bei Leopard noch lange nicht ideal. Ich freue mich auf die Erfahrung in Snow Leopard zu wiederholen, ich hoffe, sie haben dort bessere Arbeit geleistet

3

Sind iStream und oStream Instanzvariablen oder lokale Variablen? Wenn eine Instanzvariable eine starke Referenz ist, wird ein Objekt, dessen Zeiger Sie in einem Ivar speichern, am Leben bleiben (solange Ihr Objekt dies tut), aber eine lokale Variable bleibt bestehen, wenn die Funktion beendet wird, also ein Objekt Der Zeiger, den Sie in einer lokalen Variablen speichern, wird damit sterben. (Das Objekt wird bleiben, solange die Variable noch existiert und der Zeiger des Objekts immer noch darin ist, aber sobald die Funktion beendet wird und die Variable verschwindet, ist das Objekt verloren, wenn sich nichts mehr am Objekt festhält.)