You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1010 lines
34 KiB
1010 lines
34 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/NucleusLib/pnAsyncCoreExe/Private/Nt/pnAceNtFile.cpp |
|
* |
|
***/ |
|
|
|
#include "../../Pch.h" |
|
#pragma hdrstop |
|
|
|
#include "pnAceNtInt.h" |
|
|
|
|
|
namespace Nt { |
|
|
|
/**************************************************************************** |
|
* |
|
* Private |
|
* |
|
***/ |
|
|
|
// Must be a multiple of largest possible disk sector size |
|
const unsigned kSplitRwBytes = 4 * 1024 * 1024; |
|
|
|
|
|
struct NtOpFileReadWrite : Operation { |
|
NtOpFileReadWrite * masterOp; |
|
unsigned win32Bytes; |
|
AsyncNotifyFileRead rw; |
|
}; |
|
|
|
struct NtOpFileFlush : Operation { |
|
AsyncNotifyFileFlush flush; |
|
}; |
|
|
|
struct NtOpFileSequence : Operation { |
|
AsyncNotifyFileSequence sequence; |
|
}; |
|
|
|
struct NtFile : NtObject { |
|
FAsyncNotifyFileProc notifyProc; |
|
LINK(NtFile) openLink; // protected by s_fileCrit |
|
LINK(NtFile) pendLink; // protected by s_fileCrit |
|
unsigned queueWrites; |
|
unsigned sectorSizeMask; |
|
wchar_t fullPath[MAX_PATH]; |
|
|
|
NtFile (); |
|
~NtFile (); |
|
}; |
|
|
|
static long s_fileOps; |
|
static CNtCritSect s_fileCrit; |
|
static LISTDECL(NtFile, openLink) s_openFiles; |
|
static LISTDECL(NtFile, pendLink) s_pendFiles; |
|
static AsyncTimer * s_timer; |
|
|
|
|
|
//=========================================================================== |
|
inline NtFile::NtFile () { |
|
PerfAddCounter(kAsyncPerfFilesCurr, 1); |
|
PerfAddCounter(kAsyncPerfFilesTotal, 1); |
|
} |
|
|
|
//=========================================================================== |
|
NtFile::~NtFile () { |
|
PerfSubCounter(kAsyncPerfFilesCurr, 1); |
|
} |
|
|
|
//=========================================================================== |
|
static void FatalOnNonRecoverableError ( |
|
const NtOpFileReadWrite & op, |
|
unsigned error |
|
) { |
|
switch (error) { |
|
case ERROR_NO_SYSTEM_RESOURCES: |
|
case ERROR_NONPAGED_SYSTEM_RESOURCES: |
|
case ERROR_PAGED_SYSTEM_RESOURCES: |
|
case ERROR_WORKING_SET_QUOTA: |
|
case ERROR_PAGEFILE_QUOTA: |
|
case ERROR_COMMITMENT_LIMIT: |
|
return; |
|
} |
|
|
|
ASSERT((op.opType == kOpFileRead) || (op.opType == kOpFileWrite)); |
|
ErrorAssert( |
|
__LINE__, __FILE__, |
|
"Disk %s failed, error: %u", |
|
op.opType == kOpFileRead ? "read" : "write", |
|
error |
|
); |
|
} |
|
|
|
//=========================================================================== |
|
static unsigned INtFileTimerProc (void *) { |
|
if (!s_pendFiles.Head()) |
|
return INFINITE; |
|
|
|
if (!s_fileCrit.TryEnter()) |
|
return 10; |
|
|
|
// dequeue head of list |
|
NtFile * file = s_pendFiles.Head(); |
|
if (file) |
|
s_pendFiles.Unlink(file); |
|
s_fileCrit.Leave(); |
|
if (!file) |
|
return INFINITE; |
|
|
|
// retry operation |
|
ASSERT(file->opList.Head()); |
|
ASSERT((file->opList.Head()->opType == kOpQueuedFileRead) |
|
|| (file->opList.Head()->opType == kOpQueuedFileWrite) |
|
); |
|
INtFileOpCompleteQueuedReadWrite(file, (NtOpFileReadWrite *) file->opList.Head()); |
|
return 0; |
|
} |
|
|
|
//=========================================================================== |
|
static void HandleFailedOp ( |
|
NtFile * file, |
|
NtOpFileReadWrite * op, |
|
unsigned error |
|
) { |
|
ASSERT((op->opType == kOpFileRead) || (op->opType == kOpFileWrite)); |
|
|
|
// break the operation into a bunch of sub-operations if it hasn't already been done |
|
unsigned subOperations = 0; |
|
LISTDECL(NtOpFileReadWrite, link) opList; |
|
if (!op->masterOp) { |
|
// setup master operation to read the start of the buffer; this |
|
// ensures that op->rw.* is unchanged for the master operation, |
|
// which is important for the user notification callback |
|
op->masterOp = op; |
|
op->win32Bytes = min(kSplitRwBytes, op->rw.bytes); |
|
unsigned position = op->win32Bytes; |
|
|
|
// create sub-operations to read the rest of the buffer |
|
for (; position < op->rw.bytes; ++subOperations) { |
|
NtOpFileReadWrite * childOp = new NtOpFileReadWrite; |
|
childOp->overlapped.hEvent = op->overlapped.hEvent ? CreateEvent(nil, true, false, nil) : nil; |
|
childOp->overlapped.Offset = (uint32_t) ((op->rw.offset + position) & 0xffffffff); |
|
childOp->overlapped.OffsetHigh = (uint32_t) ((op->rw.offset + position) >> 32); |
|
childOp->opType = op->opType; |
|
childOp->asyncId = 0; |
|
childOp->notify = false; |
|
childOp->pending = 1; |
|
childOp->signalComplete = nil; |
|
childOp->masterOp = op; |
|
childOp->win32Bytes = min(kSplitRwBytes, op->rw.bytes - position); |
|
childOp->rw.param = nil; |
|
childOp->rw.asyncId = 0; |
|
childOp->rw.offset = op->rw.offset + position; |
|
childOp->rw.buffer = op->rw.buffer + position; |
|
childOp->rw.bytes = childOp->win32Bytes; |
|
opList.Link(childOp, kListTail); |
|
position += childOp->win32Bytes; |
|
} |
|
|
|
InterlockedExchangeAdd(&file->ioCount, (long) subOperations); |
|
} |
|
|
|
bool autoComplete = true; |
|
unsigned eventCount = 0; |
|
HANDLE events[MAXIMUM_WAIT_OBJECTS]; |
|
|
|
file->critsect.Enter(); |
|
|
|
// start with the master operation since it points to the start of the buffer |
|
NtOpFileReadWrite * childOp = op; |
|
op->pending += subOperations; |
|
for (;;) { |
|
// if we're not repeating the previous operation then dequeue a new one |
|
if (!childOp) { |
|
if (nil == (childOp = opList.Head())) |
|
break; |
|
opList.Unlink(childOp); |
|
file->opList.Link(childOp, kListLinkBefore, op); |
|
} |
|
|
|
// issue the operation |
|
bool result; |
|
const HANDLE hEvent = childOp->overlapped.hEvent; |
|
if (childOp->opType == kOpFileRead) { |
|
result = ReadFile( |
|
file->handle, |
|
childOp->rw.buffer, |
|
childOp->win32Bytes, |
|
0, |
|
&childOp->overlapped |
|
); |
|
} |
|
else { |
|
ASSERT(childOp->opType == kOpFileWrite); |
|
result = WriteFile( |
|
file->handle, |
|
childOp->rw.buffer, |
|
childOp->win32Bytes, |
|
0, |
|
&childOp->overlapped |
|
); |
|
} |
|
|
|
if (!result && ((error = GetLastError()) != ERROR_IO_PENDING)) { |
|
FatalOnNonRecoverableError(*childOp, error); |
|
|
|
if (eventCount) { |
|
LogMsg(kLogError, "HandleFailedOp1 failed"); |
|
// wait for other operations to complete on this file before retrying |
|
} |
|
else if (childOp->overlapped.hEvent) { |
|
LogMsg(kLogError, "HandleFailedOp2 failed"); |
|
// wait a while and retry operation again |
|
Sleep(10); |
|
continue; |
|
} |
|
else { |
|
// convert operation into pending operation |
|
const EOpType opType = (childOp->opType == kOpFileRead) |
|
? kOpQueuedFileRead |
|
: kOpQueuedFileWrite; |
|
childOp->opType = opType; |
|
|
|
// convert all other operations into pending operations |
|
while (nil != (childOp = opList.Head())) { |
|
childOp->opType = opType; |
|
opList.Unlink(childOp); |
|
file->opList.Link(childOp, kListLinkBefore, op); |
|
} |
|
|
|
// if there is an operation at the head of the list that will complete |
|
// without help then it will autostart the operations we queued |
|
autoComplete = file->opList.Head()->opType != opType; |
|
break; |
|
} |
|
} |
|
else { |
|
// operation was successful |
|
childOp = nil; |
|
|
|
// if we didn't fill the synchronous event array then continue issuing operations |
|
if (nil == (events[eventCount] = hEvent)) |
|
continue; |
|
if (++eventCount < arrsize(events)) |
|
continue; |
|
} |
|
|
|
// wait for all synchronous operations to complete |
|
if (eventCount) { |
|
file->critsect.Leave(); |
|
WaitForMultipleObjects(eventCount, events, true, INFINITE); |
|
for (unsigned i = 0; i < eventCount; ++i) |
|
CloseHandle(events[i]); |
|
eventCount = 0; |
|
file->critsect.Enter(); |
|
} |
|
} |
|
file->critsect.Leave(); |
|
|
|
if (eventCount) { |
|
WaitForMultipleObjects(eventCount, events, true, INFINITE); |
|
for (unsigned i = 0; i < eventCount; ++i) |
|
CloseHandle(events[i]); |
|
} |
|
else if (!autoComplete) { |
|
s_fileCrit.Enter(); |
|
s_pendFiles.Link(file, kListTail); |
|
s_fileCrit.Leave(); |
|
|
|
AsyncTimerUpdate(s_timer, 0, kAsyncTimerUpdateSetPriorityHigher); |
|
} |
|
} |
|
|
|
//=========================================================================== |
|
static void InternalFileSetSize (NtObject * file, uint64_t size) { |
|
LONG sizeHigh = (long) (size >> 32); |
|
DWORD seek = SetFilePointer(file->handle, (uint32_t) size, &sizeHigh, FILE_BEGIN); |
|
if ((seek != (DWORD) -1) || (GetLastError() == NO_ERROR)) |
|
SetEndOfFile(file->handle); |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* Module functions |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
void INtFileInitialize () { |
|
AsyncTimerCreate(&s_timer, INtFileTimerProc, INFINITE); |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileStartCleanup () { |
|
// wait until outstanding file I/O is complete |
|
for (;; Sleep(10)) { |
|
if (s_fileOps) |
|
continue; |
|
if (AsyncPerfGetCounter(kAsyncPerfFileBytesReadQueued)) |
|
continue; |
|
if (AsyncPerfGetCounter(kAsyncPerfFileBytesWriteQueued)) |
|
continue; |
|
if (volatile bool pending = (s_pendFiles.Head() != nil)) |
|
continue; |
|
break; |
|
} |
|
|
|
// slam closed any files which are still open |
|
for (;;) { |
|
s_fileCrit.Enter(); |
|
NtFile * file = s_openFiles.Head(); |
|
if (file) |
|
s_openFiles.Unlink(file); |
|
s_fileCrit.Leave(); |
|
if (!file) |
|
break; |
|
|
|
char msg[256 + MAX_PATH]; |
|
StrPrintf(msg, arrsize(msg), "Error: file '%S' still open", file->fullPath); |
|
ErrorAssert(__LINE__, __FILE__, msg); |
|
|
|
file->notifyProc = nil; |
|
INtConnCompleteOperation(file); |
|
} |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileDestroy () { |
|
if (s_timer) { |
|
AsyncTimerDelete(s_timer, kAsyncTimerDestroyWaitComplete); |
|
s_timer = nil; |
|
} |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileDelete ( |
|
NtFile * file |
|
) { |
|
file->critsect.Enter(); |
|
if (file->handle != INVALID_HANDLE_VALUE) { |
|
CloseHandle(file->handle); |
|
file->handle = INVALID_HANDLE_VALUE; |
|
} |
|
file->critsect.Leave(); |
|
|
|
delete file; |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileOpCompleteQueuedReadWrite ( |
|
NtFile * file, |
|
NtOpFileReadWrite * op |
|
) { |
|
bool result; |
|
const HANDLE hEvent = op->overlapped.hEvent; |
|
switch (op->opType) { |
|
case kOpQueuedFileRead: |
|
op->opType = kOpFileRead; |
|
// fall through |
|
|
|
case kOpFileRead: |
|
result = ReadFile( |
|
file->handle, |
|
op->rw.buffer, |
|
op->win32Bytes, |
|
0, |
|
&op->overlapped |
|
); |
|
break; |
|
|
|
case kOpQueuedFileWrite: |
|
op->opType = kOpFileWrite; |
|
// fall through |
|
|
|
case kOpFileWrite: |
|
result = WriteFile( |
|
file->handle, |
|
op->rw.buffer, |
|
op->win32Bytes, |
|
0, |
|
&op->overlapped |
|
); |
|
break; |
|
|
|
DEFAULT_FATAL(opType); |
|
} |
|
|
|
unsigned error; |
|
if (!result && ((error = GetLastError()) != ERROR_IO_PENDING)) { |
|
FatalOnNonRecoverableError(*op, error); |
|
HandleFailedOp(file, op, error); |
|
} |
|
else if (hEvent) { |
|
WaitForSingleObject(hEvent, INFINITE); |
|
CloseHandle(hEvent); |
|
} |
|
} |
|
|
|
//=========================================================================== |
|
bool INtFileOpCompleteReadWrite ( |
|
NtFile * file, |
|
NtOpFileReadWrite * op, |
|
unsigned bytes |
|
) { |
|
// adjust outstanding bytes |
|
if (bytes != op->win32Bytes) { |
|
if (!file->sectorSizeMask) |
|
ErrorAssert(__LINE__, __FILE__, "Disk %s failed", op->opType == kOpFileRead ? "read" : "write"); |
|
if (op->opType == kOpFileRead) |
|
memset(op->rw.buffer + bytes, 0, op->win32Bytes - bytes); |
|
} |
|
|
|
if (op->masterOp) { |
|
bool bail = false; |
|
file->critsect.Enter(); |
|
|
|
// if this is a child operation (!op->asyncId) then |
|
// decrement the master operation's pending count |
|
if (!op->asyncId && (--op->masterOp->pending == 1)) { |
|
if (!op->masterOp->masterOp) |
|
INtConnPostOperation(file, op->masterOp, op->masterOp->win32Bytes); |
|
} |
|
// this is the master operation; wait until all the child operations complete |
|
else if (op->pending != 1) { |
|
op->masterOp->masterOp = nil; |
|
bail = true; |
|
} |
|
file->critsect.Leave(); |
|
if (bail) |
|
return false; |
|
} |
|
|
|
// callback notification procedure if requested |
|
if (op->notify) { |
|
// before we dispatch the operation to the handler, change its |
|
// type to indicate that the operation is being dispatched |
|
op->notify = false; |
|
file->notifyProc( |
|
(AsyncFile) file, |
|
op->opType == kOpFileRead ? kNotifyFileRead : kNotifyFileWrite, |
|
&op->rw, |
|
&file->userState |
|
); |
|
} |
|
|
|
PerfSubCounter( |
|
op->opType == kOpFileRead ? kAsyncPerfFileBytesReadQueued : kAsyncPerfFileBytesWriteQueued, |
|
op->win32Bytes |
|
); |
|
return true; |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileOpCompleteFileFlush ( |
|
NtFile * file, |
|
NtOpFileFlush * op |
|
) { |
|
ASSERT(file->ioType == kNtFile); |
|
|
|
// complete flush operation |
|
if (!FlushFileBuffers(file->handle)) |
|
op->flush.error = AsyncGetLastFileError(); |
|
else |
|
op->flush.error = kFileSuccess; |
|
|
|
if (op->flush.truncateSize != kAsyncFileDontTruncate) |
|
InternalFileSetSize(file, op->flush.truncateSize); |
|
|
|
// start any queued writes which were waiting for this flush operation to |
|
// complete, but only complete any writes up to the next flush operation |
|
file->critsect.Enter(); |
|
--file->queueWrites; |
|
for (Operation * scan = file->opList.Head(); scan; scan = file->opList.Next(scan)) { |
|
if (scan->opType == kOpQueuedFileWrite) |
|
INtFileOpCompleteQueuedReadWrite(file, (NtOpFileReadWrite *) scan); |
|
else if ((scan->opType == kOpFileFlush) && (scan != op)) |
|
break; |
|
} |
|
file->critsect.Leave(); |
|
|
|
if (op->notify) { |
|
op->notify = false; |
|
file->notifyProc((AsyncFile) file, kNotifyFileFlush, &op->flush, &file->userState); |
|
} |
|
InterlockedDecrement(&s_fileOps); |
|
} |
|
|
|
//=========================================================================== |
|
void INtFileOpCompleteSequence ( |
|
NtFile * file, |
|
NtOpFileSequence * op |
|
) { |
|
if (op->notify) { |
|
op->notify = false; |
|
file->notifyProc((AsyncFile) file, kNotifyFileSequence, &op->sequence, &file->userState); |
|
} |
|
InterlockedDecrement(&s_fileOps); |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* Exported functions |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
AsyncFile NtFileOpen ( |
|
const wchar_t fullPath[], |
|
FAsyncNotifyFileProc notifyProc, |
|
EFileError * error, |
|
unsigned desiredAccess, |
|
unsigned openMode, |
|
unsigned shareModeFlags, |
|
void * userState, |
|
uint64_t * fileSize, |
|
uint64_t * fileLastWriteTime |
|
) { |
|
unsigned attributeFlags = 0; |
|
attributeFlags |= FILE_FLAG_OVERLAPPED; |
|
|
|
HANDLE handle = CreateFileW( |
|
fullPath, |
|
desiredAccess, |
|
shareModeFlags, |
|
nil, // plSecurityAttributes |
|
openMode, |
|
attributeFlags, |
|
nil // hTemplateFile |
|
); |
|
*error = AsyncGetLastFileError(); |
|
|
|
if (INVALID_HANDLE_VALUE == handle) |
|
return nil; |
|
|
|
// don't allow users to open devices like "LPT1", etc. |
|
if (GetFileType(handle) != FILE_TYPE_DISK) { |
|
LogMsg(kLogFatal, "!FILE_TYPE_DISK"); |
|
*error = kFileErrorFileNotFound; |
|
CloseHandle(handle); |
|
return nil; |
|
} |
|
|
|
// get file size |
|
DWORD sizeHi, sizeLo = GetFileSize(handle, &sizeHi); |
|
if ((sizeLo == (DWORD) -1) && (NO_ERROR != GetLastError())) { |
|
*error = AsyncGetLastFileError(); |
|
LogMsg(kLogFatal, "GetFileSize"); |
|
CloseHandle(handle); |
|
return nil; |
|
} |
|
const uint64_t size = ((uint64_t) sizeHi << (uint64_t) 32) | (uint64_t) sizeLo; |
|
|
|
uint64_t lastWriteTime; |
|
ASSERT(sizeof(lastWriteTime) >= sizeof(FILETIME)); |
|
GetFileTime(handle, nil, nil, (FILETIME *) &lastWriteTime); |
|
|
|
// allocate and initialize a new file |
|
NtFile * conn = NEWZERO(NtFile); |
|
conn->ioType = kNtFile; |
|
conn->handle = handle; |
|
conn->notifyProc = notifyProc; |
|
conn->ioCount = 1; |
|
conn->queueWrites = 0; |
|
conn->userState = userState; |
|
conn->sectorSizeMask = 0; |
|
|
|
conn->closed = false; |
|
StrCopy(conn->fullPath, fullPath, arrsize(conn->fullPath)); |
|
|
|
if (!INtConnInitialize(conn)) { |
|
*error = kFileErrorFileNotFound; |
|
conn->notifyProc = nil; |
|
INtConnCompleteOperation(conn); |
|
return nil; |
|
} |
|
|
|
// add to list of open files |
|
s_fileCrit.Enter(); |
|
s_openFiles.Link(conn); |
|
s_fileCrit.Leave(); |
|
|
|
// return out parameters |
|
if (fileSize) |
|
*fileSize = size; |
|
if (fileLastWriteTime) |
|
*fileLastWriteTime = lastWriteTime; |
|
return (AsyncFile) conn; |
|
} |
|
|
|
//=========================================================================== |
|
AsyncId NtFileRead ( |
|
AsyncFile conn, |
|
uint64_t offset, |
|
void * buffer, |
|
unsigned bytes, |
|
unsigned flags, |
|
void * param |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file->ioType == kNtFile); |
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
ASSERT((flags & (kAsyncFileRwNotify|kAsyncFileRwSync)) != (kAsyncFileRwNotify|kAsyncFileRwSync)); |
|
ASSERT(! (offset & file->sectorSizeMask)); |
|
ASSERT(! (bytes & file->sectorSizeMask)); |
|
ASSERT(! ((uintptr_t) buffer & file->sectorSizeMask)); |
|
|
|
// Normally, I/O events do not complete until both the WIN32 operation has completed |
|
// and the callback notification has occurred. A deadlock can occur if a thread attempts |
|
// to perform a series of operations and then waits for those operations to complete if |
|
// that thread holds a critical section, because all the I/O worker threads cannot |
|
// enter that critical section to complete their required notification callbacks. To |
|
// enable the sequential thread to perform a wait operation, we set the event field |
|
// into the Overlapped structure, because the event will be signaled prior to the |
|
// potentially deadlocking callback notification. |
|
NtOpFileReadWrite * op = new NtOpFileReadWrite; |
|
op->overlapped.Offset = (uint32_t) (offset & 0xffffffff); |
|
op->overlapped.OffsetHigh = (uint32_t) (offset >> 32); |
|
op->overlapped.hEvent = (flags & kAsyncFileRwSync) ? CreateEvent(nil, true, false, nil) : nil; |
|
op->opType = kOpFileRead; |
|
op->notify = (flags & kAsyncFileRwNotify) != 0; |
|
op->pending = 1; |
|
op->signalComplete = nil; |
|
op->masterOp = nil; |
|
op->win32Bytes = bytes; |
|
op->rw.param = param; |
|
op->rw.offset = offset; |
|
op->rw.buffer = (uint8_t *) buffer; |
|
op->rw.bytes = bytes; |
|
|
|
InterlockedIncrement(&file->ioCount); |
|
PerfAddCounter(kAsyncPerfFileBytesReadQueued, bytes); |
|
|
|
file->critsect.Enter(); |
|
const AsyncId asyncId = op->rw.asyncId = op->asyncId = INtConnSequenceStart(file); |
|
file->opList.Link(op, kListTail); |
|
file->critsect.Leave(); |
|
|
|
INtFileOpCompleteQueuedReadWrite(file, op); |
|
|
|
return asyncId; |
|
} |
|
|
|
//=========================================================================== |
|
// buffer must stay valid until I/O is completed |
|
AsyncId NtFileWrite ( |
|
AsyncFile conn, |
|
uint64_t offset, |
|
const void * buffer, |
|
unsigned bytes, |
|
unsigned flags, |
|
void * param |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file->ioType == kNtFile); |
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
ASSERT((flags & (kAsyncFileRwNotify|kAsyncFileRwSync)) != (kAsyncFileRwNotify|kAsyncFileRwSync)); |
|
ASSERT(! (offset & file->sectorSizeMask)); |
|
ASSERT(! (bytes & file->sectorSizeMask)); |
|
ASSERT(! ((uintptr_t) buffer & file->sectorSizeMask)); |
|
|
|
// Normally, I/O events do not complete until both the WIN32 operation has completed |
|
// and the callback notification has occurred. A deadlock can occur if a thread attempts |
|
// to perform a series of operations and then waits for those operations to complete if |
|
// that thread holds a critical section, because all the I/O worker threads cannot |
|
// enter that critical section to complete their required notification callbacks. To |
|
// enable the sequential thread to perform a wait operation, we set the event field |
|
// into the Overlapped structure, because the event will be signaled prior to the |
|
// potentially deadlocking callback notification. |
|
NtOpFileReadWrite * op = new NtOpFileReadWrite; |
|
op->overlapped.Offset = (uint32_t) (offset & 0xffffffff); |
|
op->overlapped.OffsetHigh = (uint32_t) (offset >> 32); |
|
op->overlapped.hEvent = (flags & kAsyncFileRwSync) ? CreateEvent(nil, true, false, nil) : nil; |
|
op->opType = kOpFileWrite; |
|
op->notify = (flags & kAsyncFileRwNotify) != 0; |
|
op->pending = 1; |
|
op->signalComplete = nil; |
|
op->masterOp = nil; |
|
op->win32Bytes = bytes; |
|
op->rw.param = param; |
|
op->rw.offset = offset; |
|
op->rw.buffer = (uint8_t *) buffer; |
|
op->rw.bytes = bytes; |
|
|
|
InterlockedIncrement(&file->ioCount); |
|
PerfAddCounter(kAsyncPerfFileBytesWriteQueued, bytes); |
|
|
|
// to avoid a potential deadlock, we MUST issue the write if the SYNC flag is set |
|
file->critsect.Enter(); |
|
ASSERT(!file->queueWrites || !op->overlapped.hEvent); |
|
const bool startOperation = !file->queueWrites || op->overlapped.hEvent; |
|
if (!startOperation) |
|
op->opType = kOpQueuedFileWrite; |
|
const AsyncId asyncId = op->asyncId = op->rw.asyncId = INtConnSequenceStart(file); |
|
file->opList.Link(op, kListTail); |
|
file->critsect.Leave(); |
|
|
|
if (startOperation) |
|
INtFileOpCompleteQueuedReadWrite(file, op); |
|
|
|
return asyncId; |
|
} |
|
|
|
//=========================================================================== |
|
AsyncId NtFileFlushBuffers ( |
|
AsyncFile conn, |
|
uint64_t truncateSize, |
|
bool notify, |
|
void * param |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file); |
|
ASSERT(file->ioType == kNtFile); |
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
ASSERT((truncateSize == kAsyncFileDontTruncate) || !(truncateSize & file->sectorSizeMask)); |
|
|
|
// create new operation |
|
NtOpFileFlush * op = new NtOpFileFlush; |
|
file->critsect.Enter(); |
|
|
|
// write operations cannot complete while a flush is in progress |
|
++file->queueWrites; |
|
|
|
// init Operation |
|
const AsyncId asyncId = INtConnSequenceStart(file); |
|
op->overlapped.Offset = 0; |
|
op->overlapped.OffsetHigh = 0; |
|
op->overlapped.hEvent = nil; |
|
op->opType = kOpFileFlush; |
|
op->asyncId = asyncId; |
|
op->notify = notify; |
|
op->pending = 1; |
|
op->signalComplete = nil; |
|
file->opList.Link(op, kListTail); |
|
|
|
// init OpFileFlush |
|
op->flush.param = param; |
|
op->flush.asyncId = asyncId; |
|
op->flush.error = kFileSuccess; |
|
op->flush.truncateSize = truncateSize; |
|
|
|
InterlockedIncrement(&s_fileOps); |
|
InterlockedIncrement(&file->ioCount); |
|
|
|
// if there are other operations already on the list we can't complete this one |
|
if (op != file->opList.Head()) |
|
op = nil; |
|
|
|
file->critsect.Leave(); |
|
|
|
// If the operation is at the head of the |
|
// list then issue it for immediate complete |
|
if (op) |
|
INtConnPostOperation(file, op, 0); |
|
return asyncId; |
|
} |
|
|
|
//=========================================================================== |
|
void NtFileClose ( |
|
AsyncFile conn, |
|
uint64_t truncateSize |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file); |
|
ASSERT(file->ioType == kNtFile); |
|
|
|
file->critsect.Enter(); |
|
{ |
|
{ |
|
// AsyncFileClose guarantees that when it returns the file handle will be |
|
// closed so that an immediate call to AsyncFileOpen will succeed. In order |
|
// to successfully close the file handle immediately, we must ensure that |
|
// there is be no active I/O on the file; either no operations on list, or |
|
// only operations on list which are being dispatched or have been dispatched. |
|
ASSERT(!file->pendLink.IsLinked()); |
|
for (Operation * op = file->opList.Head(); op; op = file->opList.Next(op)) { |
|
// skip completed operations |
|
if (!op->pending) |
|
continue; |
|
|
|
// skip operations which are "technically complete" |
|
if (!op->notify) |
|
continue; |
|
|
|
ErrorAssert(__LINE__, __FILE__, "AsyncFileClose: File has pending I/O!"); |
|
break; |
|
} |
|
|
|
// make sure the user doesn't attempt to close the file twice |
|
ASSERT(!file->closed); |
|
file->closed = true; |
|
} |
|
|
|
if (truncateSize != kAsyncFileDontTruncate) |
|
InternalFileSetSize(file, truncateSize); |
|
|
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
CloseHandle(file->handle); |
|
file->handle = INVALID_HANDLE_VALUE; |
|
} |
|
file->critsect.Leave(); |
|
|
|
// remove file from list of open files |
|
s_fileCrit.Enter(); |
|
ASSERT(!file->pendLink.IsLinked()); |
|
s_openFiles.Unlink(file); |
|
s_fileCrit.Leave(); |
|
|
|
INtConnCompleteOperation(file); |
|
} |
|
|
|
//=========================================================================== |
|
void NtFileSetLastWriteTime ( |
|
AsyncFile conn, |
|
uint64_t lastWriteTime |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file); |
|
ASSERT(file->ioType == kNtFile); |
|
|
|
file->critsect.Enter(); |
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
SetFileTime(file->handle, nil, nil, (FILETIME *) &lastWriteTime); |
|
file->critsect.Leave(); |
|
} |
|
|
|
//=========================================================================== |
|
uint64_t NtFileGetLastWriteTime ( |
|
const wchar_t fileName[] |
|
) { |
|
WIN32_FILE_ATTRIBUTE_DATA info; |
|
bool f = GetFileAttributesExW(fileName, GetFileExInfoStandard, &info); |
|
return f ? *((uint64_t *) &info.ftLastWriteTime) : 0; |
|
} |
|
|
|
//=========================================================================== |
|
// Inserts a "null operation" into the list of reads and writes. The callback |
|
// will be called when all preceding operations have successfully completed. |
|
AsyncId NtFileCreateSequence ( |
|
AsyncFile conn, |
|
bool notify, |
|
void * param |
|
) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(file); |
|
ASSERT(file->ioType == kNtFile); |
|
|
|
// create new operation |
|
NtOpFileSequence * op = new NtOpFileSequence; |
|
file->critsect.Enter(); |
|
|
|
// init Operation |
|
const AsyncId asyncId = INtConnSequenceStart(file); |
|
op->overlapped.Offset = 0; |
|
op->overlapped.OffsetHigh = 0; |
|
op->overlapped.hEvent = nil; |
|
op->opType = kOpSequence; |
|
op->asyncId = asyncId; |
|
op->notify = notify; |
|
op->pending = 1; |
|
op->signalComplete = nil; |
|
file->opList.Link(op, kListTail); |
|
|
|
// init OpFileSequence |
|
op->sequence.param = param; |
|
op->sequence.asyncId = asyncId; |
|
|
|
InterlockedIncrement(&s_fileOps); |
|
InterlockedIncrement(&file->ioCount); |
|
|
|
// if there are other operations already on the list we can't complete this one |
|
if (op != file->opList.Head()) |
|
op = nil; |
|
|
|
file->critsect.Leave(); |
|
|
|
// If the operation is at the head of the |
|
// list then issue it for immediate complete |
|
if (op) |
|
INtConnPostOperation(file, op, 0); |
|
return asyncId; |
|
} |
|
|
|
//=========================================================================== |
|
// This function allows the caller to wait until an I/O operation completes for |
|
// a file. However, it is an EXTREMELY DANGEROUS function, so you should follow |
|
// these rules to avoid a deadlock: |
|
// 1. AsyncWaitId CAN NEVER be called in response to an I/O completion notification |
|
// callback (a call to an FAsyncNotifyFileProc), because if all I/O threads were |
|
// blocking for I/O there would be no threads left to complete the I/O. |
|
// 2. AsyncWaitId CAN NEVER be called from a timer callback for the same reason as #1. |
|
// 3. AsyncWaitId can be called from inside an idle callback (FAsyncIdleProc), because |
|
// only half of the I/O threads can be inside an idle callback at the same time, |
|
// which leaves the other half available to complete I/O. |
|
// 4. When calling AsyncWaitId, the thread which makes the call MUST NOT hold any |
|
// locks (critical section or reader/writer locks) which would cause an I/O |
|
// thread to block while completing I/O that might be needed to complete the |
|
// I/O operation that is being waited. That means not only the specific I/O |
|
// operation that is being waited, but also any I/O that will call the same |
|
// FAsyncNotifyFileProc. |
|
// 5. Spin-blocking (calling AsyncWaitId in a loop with a small timeout value) IS NOT |
|
// a solution to the deadlock problem, it will still create a deadlock because |
|
// the I/O thread is still fully occupied and cannot complete any I/O |
|
bool NtFileWaitId (AsyncFile conn, AsyncId asyncId, unsigned timeoutMs) { |
|
NtFile * file = (NtFile *) conn; |
|
ASSERT(asyncId); |
|
ASSERT(file); |
|
ASSERT(file->ioType == kNtFile); |
|
ASSERT(file->handle != INVALID_HANDLE_VALUE); |
|
|
|
ThreadAssertCanBlock(__FILE__, __LINE__); |
|
|
|
// has the AsyncId already completed? |
|
if (file->nextCompleteSequence - (long) asyncId >= 0) |
|
return true; |
|
|
|
// is this a non-blocking wait? |
|
if (!timeoutMs) |
|
return false; |
|
|
|
// find the I/O operation the user is waiting for |
|
CNtWaitHandle * signalComplete = nil; |
|
file->critsect.Enter(); |
|
for (Operation * op = file->opList.Head(); op; op = file->opList.Next(op)) { |
|
if (asyncId != op->asyncId) |
|
continue; |
|
|
|
// create an object to wait on |
|
if (!op->signalComplete) |
|
op->signalComplete = new CNtWaitHandle; |
|
signalComplete = op->signalComplete; |
|
signalComplete->IncRef(); |
|
break; |
|
} |
|
file->critsect.Leave(); |
|
|
|
// if we didn't find or create a signal then the operation must have |
|
// completed just before we managed to enter the critical section |
|
if (!signalComplete) |
|
return true; |
|
|
|
const bool result = signalComplete->WaitForObject(timeoutMs); |
|
signalComplete->DecRef(); |
|
return result; |
|
} |
|
|
|
//============================================================================ |
|
bool NtFileSeek ( |
|
AsyncFile conn, |
|
uint64_t distance, |
|
EFileSeekFrom from |
|
) { |
|
COMPILER_ASSERT(kFileSeekFromBegin == FILE_BEGIN); |
|
COMPILER_ASSERT(kFileSeekFromCurrent == FILE_CURRENT); |
|
COMPILER_ASSERT(kFileSeekFromEnd == FILE_END); |
|
|
|
NtFile * file = (NtFile *) conn; |
|
LONG low = (LONG)(distance % 0x100000000ul); |
|
LONG high = (LONG)(distance / 0x100000000ul); |
|
uint32_t result = SetFilePointer(file->handle, low, &high, from); |
|
if ((result == (uint32_t)-1) && (GetLastError() != NO_ERROR)) { |
|
LogMsg(kLogFatal, "failed: SetFilePointer"); |
|
return false; |
|
} |
|
else |
|
return true; |
|
} |
|
|
|
} // namespace Nt
|
|
|