Asynchronous I/O mit WriteFile/GetOverlappedResult



  • Hallo,

    ich habe ein Problem mit asynchroner I/O und Named Pipes. Ich möchte Daten in eine Named Pipe schreiben, das gelingt anfangs auch. Aber irgendwann kehrt der Aufruf aus GetOverlappedResult nicht mehr zurück und die Verbindung schreibt keine weiteren Daten mehr. Solange alle Daten direkt geschrieben werden konnten ist alles in Ordnung, aber sobald WriteFile fehlschlägt und auf das Ende der Nonblocking I/O Operation gewartet werden muss geht nix mehr. Rufe ich GetOverlappedResult mit FALSE als letztem Parameter auf schlägt der Aufruf immer fehl und GetLastError gibt 996 (ERROR_IO_INCOMPLETE) zurück.

    Edit:
    Konfuse Fehlerbeschreibung passte nicht zum Quelltext

    DWORD PipeClient::write( const void* Buffer, DWORD Size )
    {
       if( Buffer && Size > 0 )
       {
          OVERLAPPED Overlapped = { 0 };
          Overlapped.hEvent = ::CreateEvent( nullptr, TRUE, FALSE, nullptr ); // manual reset
          
          DWORD BytesWritten = 0;
          if( ::WriteFile( Pipe_, Buffer, Size, &BytesWritten , &Overlapped ) )
          {
              // Schreiboperation ohne Blocking abgeschlossen
             ::CloseHandle( Overlapped.hEvent);
              return BytesWritten;
          }
          // Schreiboperatioen abgeschlossen?
          DWORD EC = ::GetLastError();
          if( EC == ERROR_IO_PENDING )
          {
             // nein, auf Abschluss warten
             if( ::GetOverlappedResult( Pipe_, &Overlapped, &BytesWritten, TRUE ) )
             {
                ::CloseHandle( Overlapped.hEvent);
                return BytesWritten;
             }
          }
          // Fehler
         ::CloseHandle( Overlapped.hEvent);
         throw runtime_error( "write() failed with error code " + to_string( ::GetLastError() );
       }
       return 0; 
    }
    

    Plan B für den Fehlschlag von WriteFile:

    for( ;; )
    {
        if( ::GetOverlappedResult( Pipe_, &Overlapped, &BytesWritten, FALSE ) )
        {
          ::CloseHandle( Overlapped.hEvent );
          return BytesWritten;
        }
        else
        {
           DWORD EC = ::GetLastError(); // ist immer 996
        }
    }
    

    Das Handle Pipe_ wurde so (erfolgreich) erzeugt :

     Pipe_ = ::CreateFile( PipeName.c_str(), 
                           GENERIC_READ|GENERIC_WRITE, 
                           0, 
                           nullptr, 
                           OPEN_EXISTING, 
                           FILE_FLAG_OVERLAPPED|FILE_FLAG_WRITE_THROUGH, 
                           nullptr );
    

    Habe keine Ahnung, woran das liegen könnte, hat da jemand eine Idee?



  • Lass dir im Fehlerfall mal Overlapped.hEvent ausgeben. Wäre zwar ziemlich krank wenn keine Events mehr erzeugt werden können, aber ist das einzige was mir zu dem Fehlerbild einfällt.

    Bzw. du kannst auch mal mit HasOverlappedIoCompleted die OVERLAPPED Struktur checken, gucken was die sagt.

    Der Code von "Plan A" sieht für mich OK aus. Also abgesehen davon dass du beim Werfen der Exception u.U. den falschen Error-Code in die Message reinformatierst (Error-Code immer sofort nach der Funktion in ne Variable speichern und dann immer nur mehr die Variable verwenden).



  • Bei dem Vorgehen im Quelltext frage ich mich warum Du überhaupt Overlapped I/O benutzt, wenn Du sowieso nach deem Schreiben wartest...



  • Erst ein Mal Danke euch beiden für die Beiträge.

    @hustbär
    Ich habe für Handles RAII Objekte, die habe ich oben durch die Windows Standarddinger ersetzt. Und durch die CloseHandle Aufrufe ist GetLastError() natürlich nicht mehr aktuell, im Produktivcode passt das aber.

    @Martin Richter
    Hab´s erst ohne Overlapped I/O gemacht, das ging direkt kaputt.

    Nach langer Suche bin ich endlich auf die Ursache gestossen, das war etwas vertrackt.
    Die Message Pump behandelt die Nachrichten, die an meinen Pipeserver und -Client versendet werden. In der Empfangsmethode des Servers wird aus den Daten eine Logmeldung erzeugt und in eine SQLite3 Datenbank geschrieben.
    In einem anderen Thread wird irgendwann eine Transaktion in der SQLite3 Datenbank gestartet, und die ist für das Problem verantwortlich. Die Transaktion lockt die ganze Datenbank, und wenn während der Transaktion eine Nachricht über den Pipeserver kommt versucht der , die Nachricht in der db abzulegen. Da die aber gelockt ist blockiert der Aufruf und kommt erst dann zurück, wenn die Transaktion abgeschlossen ist oder der Aufruf in einen Timeout läuft.