Асинхронный ReadDirectoryChangesW - GetQueuedCompletionStatus всегда истекает - PullRequest
10 голосов
/ 27 мая 2011

Точно так же, как это звучит, я пытаюсь асинхронный ReadDirectoryChangesW с IO Completion, и он не работает, в частности, GetLastError многократно возвращает 258 (GetQueuedCompletionStatus тайм-аут).

У меня есть структуры:

typedef struct dirinfo_struct
{
    HANDLE hDirFH;           // directory handle
    OVERLAPPED Overlapped;   // overlapped storage
    int len_buffer;          // buffer length
    wchar_t* buffer;         // buffer itself
    wchar_t* directory_name; // target name
} dirinfo_t;

typedef struct dirmon_struct
{
    HANDLE hDirOPPort;       // handle to the IO port.
    dirinfo_t* dirinfo;      // pointer to the struct above.
} dirmon_t;

для хранения соответствующей информации. Это инициализировано:

dirinfo_t* t = malloc(1*sizeof(dirinfo_t));
dirmon_t* d = malloc(1*sizeof(dirmon_t));
dirinfo_init(t); // does t->buffer = malloc(8192*sizeof(wchar_t));

Затем я создаю свой дескриптор каталога и com-порт:

t->hDirFH = CreateFile(L"C:\\test",
                        FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                        NULL); 
d->dirinfo = t;
d->hDirOPPort = CreateIoCompletionPort(d->dirinfo->hDirFH, 
                                       NULL,       
                                       (ULONG_PTR)(d->dirinfo), 
                                       1); 

Затем я передаю эту информацию через d в новый поток. Теперь о новой теме я имею:

bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, lpBytes, 
                                     (ULONG_PTR*)d->dirinfo,    
                                     lpOverlapped, 1000);

if ( bResultQ )
{
    bResultR = ReadDirectoryChangesW(d->dirinfo->hDirFH, 
                                     (void*)d->dirinfo->buffer, 
                                     8192, TRUE,
                                     FILE_NOTIFY_CHANGE_FILE_NAME | 
                                     FILE_NOTIFY_CHANGE_DIR_NAME |
                                     FILE_NOTIFY_CHANGE_ATTRIBUTES | 
                                     FILE_NOTIFY_CHANGE_SIZE |
                                     FILE_NOTIFY_CHANGE_LAST_WRITE | 
                                     FILE_NOTIFY_CHANGE_LAST_ACCESS | 
                                     FILE_NOTIFY_CHANGE_CREATION | 
                                     FILE_NOTIFY_CHANGE_SECURITY,
                                     lpReadDirBytes,
                                     &d->dirinfo->Overlapped,
                                     NULL );
} 
else
{
    printf("GetQueuedCompletionStatus(): Failed, ");
    errorcode = GetLastError();
    printf("Error Code %d\n", errorcode);
    Sleep(500);
}

Так что я отключил это, и я весело получаю тайм-ауты (258 ошибок), как и должен, так как каталог не изменился. Однако, даже если я изменяю каталог, я все равно получаю сообщения об ошибках; другими словами, эти изменения не обнаруживаются. Что заставляет меня верить, что я настроил это неправильно.

Есть идеи?

Предостережения:

  • По иронии судьбы, это в конечном итоге будет преобразовано в Python через pywin32. Однако, пока я не понимаю, как это должно работать в C, я не пойду туда.
  • Синхронный ReadDirectoryChangesW не вариант. Если никакие события не запускаются, мне нужен поток, для которого истекло время ожидания, чтобы он мог проверить, должен ли он все еще работать.
  • Я пишу специально на C, а не на C ++.
  • FindFirstChangeNotification и т.д. тоже не вариант - я не хочу постоянно сравнивать списки каталогов, чтобы выяснить, что изменилось.

Другие примечания:

  • Каталог существует, этот дескриптор не равен NULL. Аналогично для дескриптора компорта.
  • Все передается в поток

Я взглянул на CDirectoryChangeWatcher из проекта кода, но, используя C ++ и многие другие потоки, я не вижу, что я делаю по-другому. Не стесняйтесь указывать, если я что-то упустил, хотя!

Вывод, если он помогает, в основном повторяется, независимо от того, насколько сильно я изменяю каталог, о котором идет речь.

GetQueuedCompletionStatus(): Failed, Error Code 258

Ответы [ 2 ]

6 голосов
/ 28 мая 2011

Я понимаю, что публикация стен кода, как правило, считается ужасной, но вот как я это сделал:

Новые структуры:

BOOL runthread;

typedef struct overlapped_struct
{
    OVERLAPPED overlapped;
    wchar_t* buffer;
} overlapped_t;

typedef struct dirinfo_struct
{

    HANDLE hDirOPPort;
    HANDLE hDirFH;
    overlapped_t* o;
    int len_buffer;
    wchar_t* buffer;
    wchar_t* directory_name;
    ULONG_PTR CompletionKey;
} dirinfo_t;

int somekey = 1;

Методы размещения:

void dirinfo_init(dirinfo_t* t)
{
    t->buffer = malloc(16777216*sizeof(wchar_t));
    t->len_buffer = 16777216;
    t->o = calloc(1, sizeof(overlapped_t));
    t->o->buffer = calloc(16777216, sizeof(wchar_t));
    memset(t->o->buffer, 0, 16777216);
    memset(t->o, 0, sizeof(OVERLAPPED));
}

void dirinfo_free(dirinfo_t* t)
{
    free(t->buffer);
    free(t->o->buffer);
    free(t->o);
    free(t);
}

Важные вещи из main() делают это:

dirinfo_t* d = malloc(1*sizeof(dirinfo_t));
d->CompletionKey = (ULONG_PTR)&somekey;
dirinfo_init(d);

/* set up */
runthread = TRUE;
d->hDirFH = CreateFile(L"C:\\hydratest",
                FILE_LIST_DIRECTORY,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                NULL); 

d->hDirOPPort = CreateIoCompletionPort(d->hDirFH, NULL, 
                      (ULONG_PTR)d->CompletionKey, 1);  

Тогда, наконец, мой ожидающий поток. Вот ключ: я не передаю перекрывающуюся структуру. Я передаю структуру, содержащую OVERLAPPED плюс достаточное количество wchar_t хранилища. По причинам, которые я не до конца понимаю, это работает. Редактировать см. этот ответ . Я считаю, что область данных здесь действует как перекрывающийся буфер.

DWORD WINAPI WaitingThread(void* args)
{
    DWORD errorcode = 0;    // an error code
    BOOL bResultQ = FALSE;  // obvios=us
    BOOL bResultR = FALSE;
    DWORD NumBytes = 0; 
    FILE_NOTIFY_INFORMATION* pInfo = NULL; // the data incoming is a pointer
                                           // to this struct.
    int i = 0;
    dirinfo_t* d = (dirinfo_t*) args;      // rescue struct from thread arg.

Тогда мы перейдем к самому основному потоку. Метод проб и ошибок предполагает, что вы должны вызывать ReadDirectoryW и GetQueueCompletionStatus. Я думаю , что это значит, что мы не должны касаться буфера с ReadDirectoryChangeW **, если * нам не сказали, что мы можем к GetQueue. Однако исправления этой гипотезы приветствуются.

    while ( runthread )
    {
        bResultR = ReadDirectoryChangesW(d->hDirFH, (void*)d->buffer, 
                                          16777216, TRUE,
               FILE_NOTIFY_CHANGE_FILE_NAME  | FILE_NOTIFY_CHANGE_DIR_NAME |
               FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
               FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | 
               FILE_NOTIFY_CHANGE_CREATION   | FILE_NOTIFY_CHANGE_SECURITY,
                                          NULL,
                                          &d->o->overlapped,
                                          NULL );
        bResultQ = GetQueuedCompletionStatus(d->hDirOPPort, 
                                             &NumBytes, &(d->CompletionKey), 
                                             (LPOVERLAPPED*)(d->o), 1000);

Итак, теперь мы вызвали эти функции, мы затем проверяем, что они оба вернули true. большое уродливое предупреждение если вы правильно настроили параметры bResultR всегда возвращает true, или мне так кажется. bResultQ однако зависит от того, есть ли новые данные на порту.

        if ( bResultQ && bResultR )
        {

Итак, здесь мы приводим этот буфер из ReadDirectoryChangesW и получаем доступ к информации из структуры.

            wprintf(L"\n");
            pInfo = (FILE_NOTIFY_INFORMATION*) d->buffer;
            wprintf(L"File %s", pInfo->FileName);
            wprintf(L" changes %d\n", pInfo->Action);
            memset(d->buffer, 0, 16777216);
        }

В противном случае и благодаря Тони за это вы можете спокойно игнорировать ошибки WAIT_TIMEOUT, но все остальное, вероятно, означает, что у вас проблемы.

        else
        {
            errorcode = GetLastError();

            if ( errorcode == WAIT_TIMEOUT )
            {
                printf("GetQueuedCompletionStatus(): Timeout\n");
            }
            else
            {
                printf("GetQueuedCompletionStatus(): Failed\n");
                printf("Error Code %d\n", errorcode);
            }
            Sleep(500);
        }
    }   

    return 0;
}

И это завершает то, что я считаю рабочим примером.

Некоторые заметки:

  • Я установил огромный размер буфера. Я заметил, что при копировании примерно 100 файлов в буфере не хватает места, установленного на 8192, и пропущен один или два элемента, здесь и там. Так что я не ожидаю, что это всегда подхватит все. Мое решение было бы сказать каждые 100 событий, проверить, что дерево файлов это то, что вы думаете, используя этот метод. Однако бесконечно лучшее решение для постоянного перечисления потенциально большого дерева.
1 голос
/ 27 мая 2011

Примечание. Чтобы правильно отследить ошибки из GetQueuedCompletionStatus, так как трудно определить, что эта функция действительно вернулась, необходимо выполнить следующее:

ПРИМЕР:

DWORD dwNumBytes;
ULONG_PTR CompletionKey;
OVERLAPPED* pOverlapped;

//hIOCP is initialized somewhere else in the program
BOOL bOK = GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompletionKey, &pOverlapped, 1000);

DWORD dwError = GetLastError();

if(bOK)
{
// Process a successfully completed I/O event.
}
else
{
  if (pOverlapped != NULL)
  {
    // Process a failed completed I/O request
    //dwError contains the reason for failure
  }
  else {
      if (dwError == WAIT_TIMEOUT)
      {
         //Time-out while waiting for completed I/O entry.
      }
      else {
          //Bad call to GetQueuedCompletionStatus
          //dwError contains the reason for the bad call.
      }
}

Примервзято из книги (Windows через C / C ++). Пожалуйста, попробуйте реализовать эту обработку ошибок в своем коде.

Кроме того, "... потоки, которые вызывают GetQueuedCompletionStatus, пробуждаются в порядке очереди" (LIFO) fashion. "

OVERLAPPED Структура:

При выполнении операций ввода-вывода асинхронного устройства необходимо передать адрес инициализированному OVERLAPPED структура через параметр pOverlapped .Слово «перекрывается» в этом контексте означает, что время, затрачиваемое на выполнение запросов ввода-вывода, перекрывает время, потраченное вашим потоком на выполнение других задач.

Он говорит о параметре, когда вы вызываете ReadFile или WriteFile , как примечание к вышесказанному, которое требует инициализации этой структуры.

Это выглядит следующим образом:

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

ПРИМЕЧАНИЕ : вы передаете указатель на структуру на ваш параметр dwCompletionKey вашей функции CreateIoCompletionPort.В ссылке, на которую я смотрю, они просто передают константу (#define CK_FILE 1) для этого.В нем говорится, что вы можете передавать все, что захотите, так как ОС все равно.Просто хотел указать на это, однако.

...