Bug in Windows (10) gefunden?



  • Bonita.M schrieb:

    hustbaer schrieb:

    Mehr als ein herzhaftes LOL hat das eigentlich nicht verdient.

    Was intelligentes hast Du wohl nicht zu sagen.

    Nicht jemandem der so austeilt wie du. Ausserdem will ich ja nicht dein Weltbild erschüttern.

    Wenn du meinst eine effiziente CV Implementierung zu haben, dann lass sehen. Als Klasse mit den üblichen Funktionen, so dass man sinnvoll darüber argumentieren kann. Und wenns geht vielleicht ohne Komma-Operator oder sonstigen unüblichen Konstrukten.

    Und was deine Behauptung dass das von dir beobachtete Verhalten ein Fehler sein soll angeht... naja die ist erstmal wirklich einfach nur lachhaft. Da müsste schon ein Link zu ner Doku dabei sein wo bezüglich "Fairness" beim Aufwecken der wartenden Threads irgendwas garantiert wird. Oder wenigstens nicht-garantiertes aber aktuell implementiertes Verhalten beschrieben wird.
    Hast du sowas, dann lass sehen.

    Ich kenne nur das Gegenteil. z.B. wo Microsoft unmisverständlich dokumentiert dass Dinge wie CRITICAL_SECTIONs nicht "fair" sind. Siehe
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms682608(v=vs.85).aspx
    Der Satz der mit "There is no guarantee" anfängt.
    Hat jetzt mit WaitForSingleObject/WaitForMultipleObjects nix zu tun, aber wie gesagt nur ein Beispiel - ich bin ja hier nicht in der Bringschuld.



  • Wenn du meinst eine effiziente CV Implementierung zu haben, dann lass sehen. Als Klasse mit den üblichen Funktionen, so dass man sinnvoll darüber argumentieren kann. Und wenns geht vielleicht ohne Komma-Operator oder sonstigen unüblichen Konstrukten.

    Ich habe die Implementierung in meinem ersten Posting verlinkt. Und gg. den Komma-Operator ist meiner Meinung nach nichts einzuwenden.

    Und was deine Behauptung dass das von dir beobachtete Verhalten ein Fehler sein soll angeht... naja die ist erstmal wirklich einfach nur lachhaft. Da müsste schon ein Link zu ner Doku dabei sein wo bezüglich "Fairness" beim Aufwecken der wartenden Threads irgendwas garantiert wird.

    Aufgabe eines Multitasking-Betriebssystems ist es, Rechnezeit gleichmäßig zu verteilen (zumindest unter gleich priorisierten Threads). Microsoft hat sogar versucht genau das versucht zu realisieren. Ich habe mal LockAndReleaseThread geändert, dass es eine alphabetische Thread-ID ausgibt:

    char GetID();
    
    DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam )
    {
        for( ; ; )
        {
            WaitForSingleObject( ::hEvt, INFINITE );
            printf( "spawned thread with id %c is holding lock\n", (char)GetID() );
    
            if( !::fReleased )
                ReleaseSemaphore( ::hSema, 1, NULL ),
                ::fReleased = true;
    
            Sleep( 100 );
            SetEvent( ::hEvt );
        }
    
        return 0;
    }
    
    char GetID()
    {
        static std::unordered_map<DWORD, char> mapTIDsToIDs;
        static char                            nextId = 'A';
        DWORD                                  dwThreadId;
    
        if( mapTIDsToIDs.find( dwThreadId = GetCurrentThreadId() ) == mapTIDsToIDs.end() )
            return mapTIDsToIDs[dwThreadId] = nextId++;
        else
            return mapTIDsToIDs[dwThreadId];
    }
    

    Und siehe da, wenn Du NTHREADS auf 10 setzt, dann kommen die LockAndReleaseThreads reihum, immer in der selben Reihenfolge, CPU-Zeit.
    Dass der Hauptthread *nie* CPU_Zeit bekommt, ist ein konzeptueller Fehler vom Windows-Kernel.
    Wenn es nach deiner Lesart ging, dann wär es sogar o.k., wenn ein Thread immer wieder das Event bekomen würde und kein anderer Thread schedult würde.

    Ich kenne nur das Gegenteil. z.B. wo Microsoft unmisverständlich dokumentiert dass Dinge wie CRITICAL_SECTIONs nicht "fair" sind.

    Bei der CRITICAL_SECTION ist das was anderes, denn die synchronisiert für den Fall, dass keine Contention vorliegt im Userland. Das kann man nicht fair implemtieren, will man effizient bleiben.



  • löschen



  • Bonita.M schrieb:

    Dass der Hauptthread *nie* CPU_Zeit bekommt, ist ein konzeptueller Fehler vom Windows-Kernel.

    Nö, ist ein konzeptioneller Fehler in deiner Verwendung des Windows Kernels.

    Aber ist auch sinnlos, hier weiter zu diskutieren, sowohl Windows als auch Standard C++ an sich kommen mit einer fertig implementierten Condition Variable, die nicht nur viel effizienter, sondern vor allem auch korrekt ist...



  • Der wollte doch nur ein bischen Lob, dass er der erste und einzige ist dem bis jetzt dieser unglaubliche Bug in Win10 aufgefallen ist. Und was macht ihr, anstelle ihn zu huligen, gibts Erklärungen zum Verhalten. Und dafür hat er sich nun gestern angemeldet. Echt 'ne Schande so mit einem neuen Forenmitglied umzuspringen.

    Also vom mir bekommst du ein dickes Lob, nicht nur für den tollen Bug, sondern auch für dein vorbildliches Benehmen.



  • Erstens bin ich kein "der", sondern eine "die".
    Und zweitens ging es mir darum, eine Lösung zu finden. Die ist mir aber selbst eingefallen (siehe vorangegangener Artikel mit dem Workaround). Alles weitere war unnützes Rauschen.



  • Bonita.M schrieb:

    Erstens bin ich kein "der", sondern eine "die".

    Unverschämt und anmaßend bist du so oder so.



  • Wieder der beste Beweis das Frauen dumm sind.



  • var schrieb:

    Wieder der beste Beweis das Frauen dumm sind.

    Du Affe kannst doch nichtmal richtig Deutsch.
    Wenn, dann hieße das ", da*ss* Frauen dumm sind.".
    Also wer ist hier wohl dumm?



  • @Martin Richter: Einmal schreibst du "Ein AutoReset Event ist kein Lock!" und einmal "Ein AutoReset event ist ein Lock." 🙄 🙄



  • dot schrieb:

    Aber ist auch sinnlos, hier weiter zu diskutieren, sowohl Windows als auch Standard C++ an sich kommen mit einer fertig implementierten Condition Variable, die nicht nur viel effizienter, sondern vor allem auch korrekt ist...

    Condition-Variablen werden in Producer-Consumer-Verhältnissen eingesetzt. Dabei wied das Lock nur ganz kurz gehalten und die Verarbeitung dessen was man aus der Queue geholt hat ist entweder lang weil man CPU-Zeit eines anderen Kerns nutzen wollte, oder weil man einen blockenden Aufruf tätigt. Letztlich kommt es in der Praxis daher nie zu dem Szenario, dass *immer* irgendein Thread auf das innewohnende Mutex wartet, so dass jemand der auf die Signalisierung der CV wartet aushungert. Daher habe ich den Workaround nicht in meinen Code eingebaut.
    Ich habe meine CV in Code eingesetzt wo ein Producer und 10.000 Consumer-Threads parallel arbeiten (wobei Windows dann ziemlich in die Knie geht - Windows ist halt scheiße) - die CV arbeitet einwandfrei.
    Und einen Tick effizienter als die CV von Win32 ist meine CV auch, vor allem macht sie keine Spurious Wakesups (Stolen Wakeups können aber weiter passieren, das liegt in der Natur der Sache). Und sie ist sehr viel effizienter als die CV der MS C++ Standard Library (keine Ahnung wieso die Standard Library nicht die Win32 CV einsetzt, die viel effizienter ist als die Eigenimplementation der Standard Library).



  • Erst ein Langzeit-Test über mehrere Jahre wird zeigen, ob deine Implementierung richtig ist.



  • Agent schrieb:

    Erst ein Langzeit-Test über mehrere Jahre wird zeigen, ob deine Implementierung richtig ist.

    Was für ein Unsinn.
    Keiner entwickelt sowas und lässt das erst ein paar Jahre laufen.
    Sowas guckt man sich im Source an und denkt das durch.



  • Bonita.M schrieb:

    Agent schrieb:

    Erst ein Langzeit-Test über mehrere Jahre wird zeigen, ob deine Implementierung richtig ist.

    Was für ein Unsinn.
    Sowas guckt man sich im Source an und denkt das durch.

    LOL bist du ein Genie oder was! Bei einem Sourcecode mit solch einer Komplexität kann niemand eine 100%ige Sicherheit in der Funktionsweise garantieren.



  • Agent schrieb:

    LOL bist du ein Genie oder was! Bei einem Sourcecode mit solch einer Komplexität kann niemand eine 100%ige Sicherheit in der Funktionsweise garantieren.

    Die Implementation meiner Condvar hat ohne Header eine Länge von 125 Zeilen. Das kann man 100% korrekt implementieren.


  • Mod

    Bonita.M schrieb:

    Agent schrieb:

    LOL bist du ein Genie oder was! Bei einem Sourcecode mit solch einer Komplexität kann niemand eine 100%ige Sicherheit in der Funktionsweise garantieren.

    Die Implementation meiner Condvar hat ohne Header eine Länge von 125 Zeilen. Das kann man 100% korrekt implementieren.

    Die Windows API hat 0 Zeilen...



  • Martin Richter schrieb:

    Die Windows API hat 0 Zeilen...

    Das was ich mit der Windows-API mache ist trivial.



  • Bonita.M schrieb:

    Was für ein Unsinn.
    Keiner entwickelt sowas und lässt das erst ein paar Jahre laufen.
    Sowas guckt man sich im Source an und denkt das durch.

    Ja, richtig.
    Und man lässt es andere durchgucken.
    Deswegen sollte man es auch dokumentieren so dass es für andere schnell verständlich ist. Der Teil fehlt bei deinem Code komplett. Eine Implementierung "reverse engineeren" zu müssen um draufzukommen wie sie funktionieren soll, um dann letztendlich prüfen zu können ob sie eben wirklich funktioniert, ist uninteressant.



  • hustbaer schrieb:

    Deswegen sollte man es auch dokumentieren so dass es für andere schnell verständlich ist. Der Teil fehlt bei deinem Code komplett. Eine Implementierung "reverse engineeren" zu müssen um draufzukommen wie sie funktionieren soll, um dann letztendlich prüfen zu können ob sie eben wirklich funktioniert, ist uninteressant.

    Ich hab den Code einen amerikanischen Informatik-Professor geschickt von dem ich einen interessanten Artikel über die Implementation von POSIX Condition-Variablen für Win32 gelesen habe. Er hat den Code verstanden, hatte den Ansatz bisher noch nicht gesehen, und fand ihn interessant. Also: wenn man in der Thematik drinsteckt, also grundsätzlich weiß wie Synchronisationsprimitive arbeiten (das ist Wissen was man sich in der englischsprachigen Wikipedia anlesen kann), dann versteht man das auch. Das Thema ist einfach zu umfangreich, als dass ich die Basics in den Source reinschreiben wollte bzw. könnte; der Source wäre bestimmt viemal so groß. Kennt man die aber, versteht man auch was der Code macht. Das gleiche gilt für das kommende Transactional Memory (was es seit den Haswell-CPUs gibt und seit Syklake auch funktioniert). Wenn man damit arbeitet, dokumentiert man den Code nicht insofern als man die Basics im Umgang damit in den Code reinschreibt. Das ist eine völlig neue Denke die man erstmal kennenlernen muss.
    BTW: Das Wartende die via WaitForMultipleObjects warten gegenüber denen die via WaitForSingleObject warten insofern nachrangig behandelt werden, dass sie ggf. nie drankommen, hielt der Professor auch für einen "conceptual flaw".



  • Ich habe nicht behauptet dass man den Code nicht verstehen kann. Ich habe behauptet dass es viel umständlicher ist wenn nix dabei steht.

    Ich verstehe wie Synchronisationsprimitive funktionieren. Ich bin mir auch sicher dass ich deinen Code nachvollziehen könnte wenn ich mir die Zeit dafür nehme. Ich bin nur einfach nicht ausreichend motiviert Detektiv zu spielen. Damit meine ich nicht rauszubekommen wie Synchronisationsprimitive wie Events oder Semaphore funktionieren, sondern wie dein Code funktioniert. Bzw. genauer: funktionieren möchte. Wer sagt denn dass du alles bedacht hast, dass da keine Fehler drinnen sind? Und wenn Fehler drinnen sind, wie soll ein Fremder dann verstehen was du machen wolltest wenn du es nicht mit Kommentaren (oder einer externen Doku) beschreibst. Gerade wenn du schreibst dass es ein neuer Ansatz ist sollte klar sein dass man den nicht kennen wird.

    z.B. was bedeutet "Owners()" und was "Waiters()". Dass du hier zwei getrennte Variablen in einem int64 ablegst, damit du sie gleichzeitig atomat ansprechen/updaten kannst, ist klar. Aber die Bedeutung der Variablen ist nicht klar.
    Wenn ich mir z.B. das ansehe:

    void CondVar::Enter()
    {
        if( m_dwOwningThreadId == FastGetCurrentThreadId() )
        {
            m_dwRecursionCount++;
            return;
        }
    
        LONGLONG llOwnersAndWaiters = ::InterlockedIncrement64( &m_llOwnersAndWaiters  );
    
        assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ));
    ...
    

    Der "recursive lock" Teil ist klar.
    Dann machst du mit dem InterlockedIncrement quasi "owners++". Das muss man sich aber schonmal raussuchen (gucken wie Owners() und Waiters() implementiert sind), weil man sonst nicht wissen kann was das InterlockedIncrement verändert. Das könnte schonmal als Kommentar dabeistehen.
    Und dann das assert...

    Wenn ich es mal umschreibe steht hier sinngemäss inetwa

    /* snip recursive lock part */
        m_owners++;
        assert(m_owners > m_waiters);
    

    Das ist erstmal total WTF???
    Klar, wenn man dann etwas nachdenkt kommt man drauf dass es nicht mehr Threads geben darf die in Wait() hängen als die bereits erfolgreich Enter() aufgerufen haben. Das kann man aber auch dazuschreiben.

    BTW:
    Das...

    m_dwOwningThreadId   = 0;
        dwSavedRecusionCount = m_dwRecursionCount;
        m_dwRecursionCount   = 0;
    

    ist einfach nur ein Fehler.
    Den Fall dass Wait mit dwSavedRecusionCount > 1 aufgerufen wird solltest du einfach nur mit assert() verbieten bzw. ggf. abort() aufrufen. Die Mutex einfach so aufzumachen ist mMn. einfach nur falsch. Grob falsch. Gefährlich falsch.

    Dann der Loop hier

    if( Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) )
            for( ; !::SetEvent( m_xhEvtEnter.h ); assert(false) );
    

    ist auch mehr als nur fragwürdig.
    Wenn SetEvent fehlschlägt kannst du das hier nicht sinnvoll behandeln. Ein assert() ist OK, aber der folgende Retry ist es NICHT.

    Ich bin sicher dass es da noch einiges gibt. Aber da du kein Interesse daran zu haben scheibst deinen Code gut lesbar und/oder verstehbar zu machen, hab ich auch kein grosses Interesse daran deinen Code durchzugucken.


Anmelden zum Antworten