/**************************************************************************** * * disk32.c * * routines do to queued, asynchrous disk I/O in Win32 * NOTE: these routines exist because Chicago does not yet * support overlapped io. * ***************************************************************************/ #include #include //#include #define _INC_MMDEBUG_CODE_ TRUE #include "mmdebug.h" // AuxDebug & assert macros #include "disk32.h" #define LockList(pHead) EnterCriticalSection (&pHead->csList) #define UnlockList(pHead) LeaveCriticalSection (&pHead->csList) /*+ QueueInitalize * * Initalize the queue. * *-========================================================================*/ BOOL WINAPI QueueInitialize ( PQHEAD pHead ) { InitializeCriticalSection (&pHead->csList); if ( ! pHead->hEvtElms) pHead->hEvtElms = CreateEvent (NULL, TRUE, FALSE, NULL); // a queue being initalized SHOULD be empty. be sure of it. // assert (pHead->qe.pNext == NULL || pHead->qe.pNext == &pHead->qe); assert (pHead->qe.pPrev == NULL || pHead->qe.pNext == &pHead->qe); pHead->qe.pPrev = pHead->qe.pNext = &pHead->qe; return TRUE; } /*+ QueueDelete * * de-Initalize the queue * *-========================================================================*/ BOOL WINAPI QueueDelete ( PQHEAD pHead ) { DeleteCriticalSection (&pHead->csList); if ( ! pHead->hEvtElms) { SetEvent (pHead->hEvtElms); // just in case. CloseHandle (pHead->hEvtElms); pHead->hEvtElms = NULL; } // a queue being Deleted SHOULD be empty. be sure of it. // assert (pHead->qe.pNext == &pHead->qe); assert (pHead->qe.pPrev == &pHead->qe); pHead->qe.pPrev = pHead->qe.pNext = &pHead->qe; return TRUE; } /*+ QueueInsert * * insert an element in the queue. If a thread is waiting on the queue * it will be awakened. * *-========================================================================*/ VOID WINAPI QueueInsert ( PQHEAD pHead, PQELM pqe ) { PQELM pqeHead = &pHead->qe; // this can only happen if the queue has never been initialized // assert (pqeHead->pNext != NULL); assert (pqeHead->pPrev != NULL); LockList (pHead); // insert the new element into the list at the tail. // pqe->pNext = pqeHead; pqe->pPrev = pqeHead->pPrev; pqe->pPrev->pNext = pqe; pqeHead->pPrev = pqe; // if the element we just inserted at the tail of the list // is also at the head of the list. The list must have been // empty before. In this case we want to signal the Event // to wake any threads waiting on the queue. // if ((pqeHead->pNext == pqe) && pHead->hEvtElms) SetEvent (pHead->hEvtElms); UnlockList (pHead); return; } /*+ QueueRemove * * remove an element from the queue. if the queue is empty. * wait for an element to be inserted. A timeout of 0 can be * used to POLL the queue. * *-========================================================================*/ PQELM WINAPI QueueRemove ( PQHEAD pHead, DWORD dwTimeout ) { PQELM pqe; LockList (pHead); // next & prev can only be null when a queue is un-initialized. // assert (pHead->qe.pNext != NULL); assert (pHead->qe.pPrev != NULL); // if the list is empty and the user specified a non-zero // timeout and we have a list semaphore available, wait // for the semaphore to be signalled. // pqe = pHead->qe.pNext; if ((pqe == &pHead->qe) && (dwTimeout != 0) && (pHead->hEvtElms != NULL)) { // the queue is empty - so make sure that the event has // not been signalled. // ResetEvent (pHead->hEvtElms); // unlock the list before waiting so that we dont // deadlock the thread that is inserting things into // the list. // UnlockList (pHead); AuxDebugEx (3, DEBUGLINE "Waiting (%d) secs on queue %08x\r\n", dwTimeout, pHead); WaitForSingleObject (pHead->hEvtElms, dwTimeout); LockList (pHead); pqe = pHead->qe.pNext; } // if the queue is still empty, set pqe to NULL so that we will // return null. otherwise remove the head of the queue and return // it. // if (pqe == &pHead->qe) pqe = NULL; else { // remove the element from the list. // pHead->qe.pNext = pqe->pNext; pqe->pNext->pPrev = pqe->pPrev; // just to be careful, blank out the // pqe->pPrev = pqe->pNext = NULL; // if the queue is now empty, reset the event // if ((pHead->qe.pNext == &pHead->qe) && pHead->hEvtElms) ResetEvent (pHead->hEvtElms); } UnlockList (pHead); return pqe; } #ifdef DEBUG /*+ QueueDump * *-========================================================================*/ void WINAPI QueueDump ( PQHEAD pHead ) { PQELM pqe; UINT nMax; LockList (pHead); AuxDebugEx (2, "Dumping Queue %08X\r\n", pHead); AuxDebugEx (2, "\telm %08x (next=%08x, prev=%08x)\r\n", &pHead->qe, pHead->qe); nMax = 10; pqe = pHead->qe.pNext; while (nMax && pqe != &pHead->qe) { pqe = pqe->pNext; --nMax; AuxDebugEx (2, "\telm %08x (next=%08x, prev=%08x)\r\n", pqe, *pqe); } UnlockList (pHead); } #endif // DEBUG /*+ SequentialIOThreadProc * * thread proc dos sequential writes to a file from buffers queued * to it. * *-========================================================================*/ DWORD WINAPI AsyncIOThreadProc ( LPQIO lpqio ) { // we loop forever. exit from this loop is when // QueueRemove returns a qiobuf with cb == 0 // for (;;) { PQIOBUF pqBuf; DWORD dwOff; // get the next buffer to be written. if we are running // down, then dont bother to wait for more buffers if // the queue is empty. // pqBuf = (LPVOID)QueueRemove (&lpqio->que, INFINITE); assert (!pqBuf || !IsBadWritePtr(pqBuf, sizeof(*pqBuf))); // if we got no buffer back from queue remove, this may be reasonable // in the case of two threads, just loop back and try again. // if ( ! pqBuf ) continue; // break out of the loop when a -1 buffer pointer is queued // if ( pqBuf->lpv == (LPVOID)-1) { QueueInsert (&lpqio->queDone, (LPVOID)pqBuf); break; } AuxDebugEx (2, DEBUGLINE "tid %08X %s %X bytes at %08X into %08X\r\n", lpqio->tid, pqBuf->bWrite ? "Writing" : "Reading", pqBuf->cb, pqBuf->dwOffset, pqBuf->lpv); assert3 (!pqBuf->cb || HIWORD(pqBuf->lpv), "QioThread - invalid buffer %08X", pqBuf->lpv); if ( HIWORD(pqBuf->lpv) && pqBuf->cb ) { assert (!IsBadReadPtr(pqBuf->lpv, pqBuf->cb)); dwOff = SetFilePointer (lpqio->hFile, pqBuf->dwOffset, NULL, FILE_BEGIN); if (dwOff != pqBuf->dwOffset) { pqBuf->dwError = GetLastError(); AuxDebug2 ("avifile32 seek error %d", pqBuf->dwError); } else { if (pqBuf->bWrite) { if ( ! WriteFile (lpqio->hFile, pqBuf->lpv, pqBuf->cb, &pqBuf->cbDone, NULL) || (pqBuf->cb != pqBuf->cbDone)) { pqBuf->dwError = GetLastError(); AuxDebug2 ("avifile32 write error %d", pqBuf->dwError); } else pqBuf->dwError = 0; } else { if ( ! ReadFile (lpqio->hFile, pqBuf->lpv, pqBuf->cb, &pqBuf->cbDone, NULL) || (pqBuf->cb != pqBuf->cbDone)) { pqBuf->dwError = GetLastError(); AuxDebug2 ("avifile32 read error %d", pqBuf->dwError); } else pqBuf->dwError = 0; } } } // Once the write is done, but the buffer on the done queue // QueueInsert (&lpqio->queDone, (LPVOID)pqBuf); } return 0; } /*+ QioInitialize * * Open a file for queued sequential io. * *-========================================================================*/ BOOL WINAPI QioInitialize ( LPQIO lpqio, HANDLE hFile, int nPrio ) { DebugSetOutputLevel (GetProfileInt("debug", "avifil32", 0)); AuxDebugEx (1, DEBUGLINE "QioInitialize (%08x, %d)\r\n", lpqio, nPrio); nPrio = max(nPrio, THREAD_PRIORITY_IDLE); nPrio = min(nPrio, THREAD_PRIORITY_TIME_CRITICAL); QueueInitialize (&lpqio->que); QueueInitialize (&lpqio->queDone); assert ( ! lpqio->hThread); lpqio->hFile = hFile; lpqio->nPrio = nPrio; lpqio->hThread = CreateThread (NULL, 0, AsyncIOThreadProc, lpqio, 0, &lpqio->tid); // if we fail creating the thread, cleanup and // return error. // if ( ! lpqio->hThread) { AuxDebugEx (1, DEBUGLINE "QioInitialize - CreateThread failed\r\n", lpqio, nPrio); QueueDelete (&lpqio->que); QueueDelete (&lpqio->queDone); ZeroMemory (lpqio, sizeof(*lpqio)); return FALSE; } SetThreadPriority (lpqio->hThread, lpqio->nPrio); return TRUE; } /*+ QioAdd * * *-========================================================================*/ BOOL WINAPI QioAdd ( LPQIO lpqio, PQIOBUF pqBuf ) { assert (lpqio); assert (pqBuf); assert (lpqio->que.qe.pNext != NULL); assert (lpqio->hThread); if (!lpqio->hThread) return FALSE; // the queue insert/remove code in this function make the // assumption that the queue pointers are the first element // of the pqBuf structure. // assert ((DWORD)&pqBuf->qe - (DWORD)pqBuf == 0); // not allowed to queue a buffer with no size or no pointer // assert (HIWORD(pqBuf->lpv)); assert (pqBuf->cb); pqBuf->bPending = TRUE; QueueInsert (&lpqio->que, (LPVOID)pqBuf); return TRUE; } /*+ QioWait * * *-========================================================================*/ BOOL WINAPI QioWait ( LPQIO lpqio, PQIOBUF pqBufWait, BOOL bWait ) { assert (lpqio); assert (pqBufWait); assert (lpqio->que.qe.pNext != NULL); assert (lpqio->hThread); // the queue insert/remove code in this function make the // assumption that the queue pointers are the first element // of the pqBuf structure. // assert ((DWORD)&pqBufWait->qe - (DWORD)pqBufWait == 0); if (pqBufWait->bPending) { PQIOBUF pqBufT; DWORD dwTimeout = bWait ? INFINITE : 0; do { pqBufT = (LPVOID) QueueRemove (&lpqio->queDone, dwTimeout); AuxDebugEx (4, DEBUGLINE "QioWait(%08X) - removed %08X\r\n", lpqio, pqBufT); assert (!pqBufT || !IsBadWritePtr(pqBufT, sizeof(*pqBufT))); if (!pqBufT) return FALSE; pqBufT->bPending = FALSE; } while (pqBufT != pqBufWait); } return TRUE; } /*+ QioCommit * * Waits for the Qio thread to complete any i/o that is in it's queue. * then causes the qio thread to exit and waits for it to do so. * *-========================================================================*/ BOOL WINAPI QioCommit ( LPQIO lpqio ) { QIOBUF qb; assert (lpqio); AuxDebugEx (2, DEBUGLINE "QioCommit (%08X)\r\n", lpqio); // queue up a zero size buffer as a placeholder // and then wait for the placeholder to be moved // to the done queue. // ZeroMemory (&qb, sizeof(qb)); qb.bPending = TRUE; QueueInsert (&lpqio->que, (LPVOID)&qb); return QioWait (lpqio, &qb, TRUE); } /*+ QioShutdown * * Waits for the Qio thread to complete any i/o that is in it's queue. * then causes the qio thread to exit and waits for it to do so. * *-========================================================================*/ BOOL WINAPI QioShutdown ( LPQIO lpqio ) { QIOBUF qb; assert (lpqio); AuxDebugEx (1, DEBUGLINE "QioShutdown (%08X)\r\n", lpqio); if ( ! lpqio->hThread) { AuxDebugEx (1, DEBUGLINE "QioShutdown - nothing to do!\r\n"); return TRUE; } assert (lpqio->hThread); // queue up a zero size buffer to tell the write thread to quit // ZeroMemory (&qb, sizeof(qb)); qb.lpv = (LPVOID)-1; qb.bPending = TRUE; QueueInsert (&lpqio->que, (LPVOID)&qb); QioWait (lpqio, &qb, TRUE); // wait for the thread to shut down. // AuxDebugEx (1, DEBUGLINE "Waiting for QIO thread\r\n"); WaitForSingleObject (lpqio->hThread, INFINITE); AuxDebugEx (1, DEBUGLINE "closeing thread handle\r\n"); CloseHandle (lpqio->hThread), lpqio->hThread = NULL; //INLINE_BREAK; // finally, delete the queues // QueueDelete (&lpqio->que); QueueDelete (&lpqio->queDone); return TRUE; }