1
0
forked from 0ad/0ad

fix waio error handling and update comments

This was SVN commit r7234.
This commit is contained in:
janwas 2009-12-30 14:28:24 +00:00
parent 8d94e1f0e3
commit 5f56ec86e9
2 changed files with 118 additions and 79 deletions

View File

@ -49,7 +49,8 @@ const uintptr_t sectorSize = 0x1000;
// note: the Windows lowio file descriptor limit is currrently 2048. // note: the Windows lowio file descriptor limit is currrently 2048.
/** /**
* thread-safe association between POSIX file descriptor and Win32 HANDLE * association between POSIX file descriptor and Win32 HANDLE.
* NB: callers must ensure thread safety.
**/ **/
class HandleManager class HandleManager
{ {
@ -61,26 +62,27 @@ public:
{ {
debug_assert(fd > 2); debug_assert(fd > 2);
debug_assert(GetFileSize(hFile, 0) != INVALID_FILE_SIZE); debug_assert(GetFileSize(hFile, 0) != INVALID_FILE_SIZE);
WinScopedLock lock(WAIO_CS);
std::pair<Map::iterator, bool> ret = m_map.insert(std::make_pair(fd, hFile)); std::pair<Map::iterator, bool> ret = m_map.insert(std::make_pair(fd, hFile));
debug_assert(ret.second); // fd better not already have been associated debug_assert(ret.second); // fd better not already have been associated
} }
void Dissociate(int fd) void Dissociate(int fd)
{ {
WinScopedLock lock(WAIO_CS);
const size_t numRemoved = m_map.erase(fd); const size_t numRemoved = m_map.erase(fd);
debug_assert(numRemoved == 1); debug_assert(numRemoved == 1);
} }
bool IsAssociated(int fd) const
{
return m_map.find(fd) != m_map.end();
}
/** /**
* @return aio handle associated with file descriptor or * @return aio handle associated with file descriptor or
* INVALID_HANDLE_VALUE if there is none. * INVALID_HANDLE_VALUE if there is none.
**/ **/
HANDLE Get(int fd) const HANDLE Get(int fd) const
{ {
WinScopedLock lock(WAIO_CS);
Map::const_iterator it = m_map.find(fd); Map::const_iterator it = m_map.find(fd);
if(it == m_map.end()) if(it == m_map.end())
return INVALID_HANDLE_VALUE; return INVALID_HANDLE_VALUE;
@ -96,7 +98,7 @@ static HandleManager* handleManager;
// (re)open file in asynchronous mode and associate handle with fd. // (re)open file in asynchronous mode and associate handle with fd.
int aio_reopen(int fd, const wchar_t* pathname, int oflag, ...) static LibError aio_reopen(int fd, const wchar_t* pathname, int oflag, ...)
{ {
WinScopedPreserveLastError s; // CreateFile WinScopedPreserveLastError s; // CreateFile
@ -121,40 +123,43 @@ int aio_reopen(int fd, const wchar_t* pathname, int oflag, ...)
const DWORD flags = FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING|FILE_FLAG_SEQUENTIAL_SCAN; const DWORD flags = FILE_FLAG_OVERLAPPED|FILE_FLAG_NO_BUFFERING|FILE_FLAG_SEQUENTIAL_SCAN;
const HANDLE hFile = CreateFileW(pathname, access, share, 0, create, flags, 0); const HANDLE hFile = CreateFileW(pathname, access, share, 0, create, flags, 0);
if(hFile == INVALID_HANDLE_VALUE) if(hFile == INVALID_HANDLE_VALUE)
WARN_RETURN(-1); return LibError_from_GLE();
handleManager->Associate(fd, hFile); {
return 0; WinScopedLock lock(WAIO_CS);
handleManager->Associate(fd, hFile);
}
return INFO::OK;
} }
int aio_close(int fd) static LibError aio_close(int fd)
{ {
HANDLE hFile = handleManager->Get(fd); HANDLE hFile;
// early out for files that were never re-opened for AIO. {
// since there is no way for wposix close to know this, we mustn't WinScopedLock lock(WAIO_CS);
// return an error (which would cause it to WARN_ERR). if(!handleManager->IsAssociated(fd)) // wasn't opened for aio
if(hFile == INVALID_HANDLE_VALUE) return INFO::SKIPPED;
return 0; hFile = handleManager->Get(fd);
handleManager->Dissociate(fd);
}
if(!CloseHandle(hFile)) if(!CloseHandle(hFile))
WARN_RETURN(-1); WARN_RETURN(ERR::INVALID_HANDLE);
handleManager->Dissociate(fd); return INFO::OK;
return 0;
} }
// do we want to open a second AIO-capable handle? // do we want to open a second aio-capable handle?
static bool isAioPossible(int fd, bool is_com_port, int oflag) static bool IsAioPossible(int fd, bool is_com_port, int oflag)
{ {
// stdin/stdout/stderr // stdin/stdout/stderr
if(fd <= 2) if(fd <= 2)
return false; return false;
// COM port - we don't currently need AIO access for those, and // COM port - we don't currently need aio access for those, and
// aio_reopen's CreateFile would fail with "access denied". // aio_reopen's CreateFileW would fail with "access denied".
if(is_com_port) if(is_com_port)
return false; return false;
@ -176,12 +181,12 @@ int sys_wopen(const wchar_t* pathname, int oflag, ...)
va_end(args); va_end(args);
} }
WinScopedPreserveLastError s; // _wopen's CreateFile WinScopedPreserveLastError s; // _wopen's CreateFileW
int fd = _wopen(pathname, oflag, mode); int fd = _wopen(pathname, oflag, mode);
// none of the above apply; now re-open the file. // if possible, re-open the file for aio (this works because
// note: this is possible because _open defaults to DENY_NONE sharing. // the initial _wopen defaults to DENY_NONE sharing)
if(isAioPossible(fd, false, oflag)) if(IsAioPossible(fd, false, oflag))
WARN_ERR(aio_reopen(fd, pathname, oflag)); WARN_ERR(aio_reopen(fd, pathname, oflag));
// CRT doesn't like more than 255 files open. // CRT doesn't like more than 255 files open.
@ -199,11 +204,7 @@ int close(int fd)
{ {
debug_assert(3 <= fd && fd < 256); debug_assert(3 <= fd && fd < 256);
// note: there's no good way to notify us that <fd> wasn't opened for (void)aio_close(fd); // no-op if fd wasn't opened for aio
// AIO, so we could skip aio_close. storing a bit in the fd is evil and
// a fd -> info map is redundant (waio already has one).
// therefore, we require aio_close to fail gracefully.
WARN_ERR(aio_close(fd));
return _close(fd); return _close(fd);
} }
@ -317,13 +318,14 @@ private:
}; };
// called by aio_read, aio_write, and lio_listio // called by aio_read, aio_write, and lio_listio.
// cb->aio_lio_opcode specifies desired operation // cb->aio_lio_opcode specifies desired operation.
static int aio_issue(struct aiocb* cb) // @return LibError, also setting errno in case of failure.
static LibError aio_issue(struct aiocb* cb)
{ {
// no-op from lio_listio // no-op (probably from lio_listio)
if(!cb || cb->aio_lio_opcode == LIO_NOP) if(!cb || cb->aio_lio_opcode == LIO_NOP)
return 0; return INFO::SKIPPED;
// extract aiocb fields for convenience // extract aiocb fields for convenience
const bool isWrite = (cb->aio_lio_opcode == LIO_WRITE); const bool isWrite = (cb->aio_lio_opcode == LIO_WRITE);
@ -334,24 +336,33 @@ static int aio_issue(struct aiocb* cb)
// Win32 requires transfers to be sector-aligned. // Win32 requires transfers to be sector-aligned.
if(!IsAligned(ofs, sectorSize) || !IsAligned(buf, sectorSize) || !IsAligned(size, sectorSize)) if(!IsAligned(ofs, sectorSize) || !IsAligned(buf, sectorSize) || !IsAligned(size, sectorSize))
WARN_RETURN(-EINVAL);
const HANDLE hFile = handleManager->Get(fd);
if(hFile == INVALID_HANDLE_VALUE)
{ {
errno = -EINVAL; errno = EINVAL;
WARN_RETURN(-1); WARN_RETURN(ERR::INVALID_PARAM);
} }
debug_assert(!cb->impl); // fail if aiocb is already in use (forbidden by SUSv3) HANDLE hFile;
{
WinScopedLock lock(WAIO_CS);
hFile = handleManager->Get(fd);
}
if(hFile == INVALID_HANDLE_VALUE)
{
errno = EINVAL;
WARN_RETURN(ERR::INVALID_HANDLE);
}
debug_assert(!cb->impl); // SUSv3 requires that the aiocb not be in use
cb->impl.reset(new aiocb::Impl); cb->impl.reset(new aiocb::Impl);
LibError ret = cb->impl->Issue(hFile, ofs, buf, size, isWrite); LibError ret = cb->impl->Issue(hFile, ofs, buf, size, isWrite);
if(ret < 0) if(ret < 0)
{ {
LibError_set_errno(ret); LibError_set_errno(ret);
WARN_RETURN(-1); return ret;
} }
return 0;
return INFO::OK;
} }
@ -362,17 +373,19 @@ int aio_error(const struct aiocb* cb)
} }
// get bytes transferred. call exactly once for each op. // get bytes transferred. call exactly once for each issued request.
ssize_t aio_return(struct aiocb* cb) ssize_t aio_return(struct aiocb* cb)
{ {
debug_assert(cb->impl->HasCompleted()); // SUSv3 says we mustn't be callable before IO completes // SUSv3 says we mustn't be callable before the request has completed
debug_assert(cb->impl);
debug_assert(cb->impl->HasCompleted());
size_t bytesTransferred; size_t bytesTransferred;
LibError ret = cb->impl->GetResult(&bytesTransferred); LibError ret = cb->impl->GetResult(&bytesTransferred);
cb->impl.reset(); // disallow calling again, as required by SUSv3 cb->impl.reset(); // disallow calling again, as required by SUSv3
if(ret < 0) if(ret < 0)
{ {
LibError_set_errno(ret); LibError_set_errno(ret);
WARN_RETURN(-1); return (ssize_t)-1;
} }
return (ssize_t)bytesTransferred; return (ssize_t)bytesTransferred;
} }
@ -381,7 +394,11 @@ ssize_t aio_return(struct aiocb* cb)
int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* ts) int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* ts)
{ {
if(n <= 0 || n > MAXIMUM_WAIT_OBJECTS) if(n <= 0 || n > MAXIMUM_WAIT_OBJECTS)
WARN_RETURN(-1); {
WARN_ERR(ERR::INVALID_PARAM);
errno = EINVAL;
return -1;
}
// build array of event handles // build array of event handles
HANDLE hEvents[MAXIMUM_WAIT_OBJECTS]; HANDLE hEvents[MAXIMUM_WAIT_OBJECTS];
@ -392,6 +409,7 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t
continue; continue;
aiocb::Impl* impl = cbs[i]->impl.get(); aiocb::Impl* impl = cbs[i]->impl.get();
debug_assert(impl);
if(!impl->HasCompleted()) if(!impl->HasCompleted())
hEvents[numPendingIos++] = impl->Event(); hEvents[numPendingIos++] = impl->Event();
} }
@ -401,7 +419,7 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t
const BOOL waitAll = FALSE; const BOOL waitAll = FALSE;
// convert timespec to milliseconds (ts == 0 => no timeout) // convert timespec to milliseconds (ts == 0 => no timeout)
const DWORD timeout = ts? (DWORD)(ts->tv_sec*1000 + ts->tv_nsec/1000000) : INFINITE; const DWORD timeout = ts? (DWORD)(ts->tv_sec*1000 + ts->tv_nsec/1000000) : INFINITE;
DWORD result = WaitForMultipleObjects((DWORD)numPendingIos, hEvents, waitAll, timeout); const DWORD result = WaitForMultipleObjects((DWORD)numPendingIos, hEvents, waitAll, timeout);
for(size_t i = 0; i < numPendingIos; i++) for(size_t i = 0; i < numPendingIos; i++)
ResetEvent(hEvents[i]); ResetEvent(hEvents[i]);
@ -409,10 +427,12 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t
switch(result) switch(result)
{ {
case WAIT_FAILED: case WAIT_FAILED:
WARN_RETURN(-1); WARN_ERR(ERR::FAIL);
errno = EIO;
return -1;
case WAIT_TIMEOUT: case WAIT_TIMEOUT:
errno = -EAGAIN; errno = EAGAIN;
return -1; return -1;
default: default:
@ -424,14 +444,22 @@ int aio_suspend(const struct aiocb* const cbs[], int n, const struct timespec* t
int aio_cancel(int fd, struct aiocb* cb) int aio_cancel(int fd, struct aiocb* cb)
{ {
// Win32 limitation: can't cancel single transfers - // Win32 limitation: can't cancel single transfers -
// all pending reads on this file are cancelled. // all pending reads on this file are canceled.
UNUSED2(cb); UNUSED2(cb);
const HANDLE hFile = handleManager->Get(fd); HANDLE hFile;
{
WinScopedLock lock(WAIO_CS);
hFile = handleManager->Get(fd);
}
if(hFile == INVALID_HANDLE_VALUE) if(hFile == INVALID_HANDLE_VALUE)
WARN_RETURN(-1); {
WARN_ERR(ERR::INVALID_HANDLE);
errno = EINVAL;
return -1;
}
CancelIo(hFile); WARN_IF_FALSE(CancelIo(hFile));
return AIO_CANCELED; return AIO_CANCELED;
} }
@ -439,24 +467,30 @@ int aio_cancel(int fd, struct aiocb* cb)
int aio_read(struct aiocb* cb) int aio_read(struct aiocb* cb)
{ {
cb->aio_lio_opcode = LIO_READ; cb->aio_lio_opcode = LIO_READ;
return aio_issue(cb); return (aio_issue(cb) < 0)? 0 : -1;
} }
int aio_write(struct aiocb* cb) int aio_write(struct aiocb* cb)
{ {
cb->aio_lio_opcode = LIO_WRITE; cb->aio_lio_opcode = LIO_WRITE;
return aio_issue(cb); return (aio_issue(cb) < 0)? 0 : -1;
} }
int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* UNUSED(se)) int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* se)
{ {
debug_assert(mode == LIO_WAIT || mode == LIO_NOWAIT);
UNUSED2(se); // signaling is not implemented.
for(int i = 0; i < n; i++) for(int i = 0; i < n; i++)
RETURN_ERR(aio_issue(cbs[i])); {
if(aio_issue(cbs[i]) < 0)
return -1;
}
if(mode == LIO_WAIT) if(mode == LIO_WAIT)
RETURN_ERR(aio_suspend(cbs, n, 0)); return aio_suspend(cbs, n, 0);
return 0; return 0;
} }
@ -464,7 +498,9 @@ int lio_listio(int mode, struct aiocb* const cbs[], int n, struct sigevent* UNUS
int aio_fsync(int, struct aiocb*) int aio_fsync(int, struct aiocb*)
{ {
WARN_RETURN(-ENOSYS); WARN_ERR(ERR::NOT_IMPLEMENTED);
errno = ENOSYS;
return -1;
} }

View File

@ -27,21 +27,22 @@
#include "no_crt_posix.h" #include "no_crt_posix.h"
// Note: for maximum efficiency, transfer buffers, offsets, and lengths // Note: transfer buffers, offsets, and lengths must be sector-aligned
// should be sector aligned (otherwise, buffer is copied). // (we don't bother copying to an align buffer because the file cache
// already requires splitting IOs into aligned blocks)
// //
// <signal.h> // <signal.h>
// //
union sigval union sigval // unused
{ {
int sival_int; // Integer signal value. int sival_int; // Integer signal value.
void* sival_ptr; // Pointer signal value. void* sival_ptr; // Pointer signal value.
}; };
struct sigevent struct sigevent // unused
{ {
int sigev_notify; // notification mode int sigev_notify; // notification mode
int sigev_signo; // signal number int sigev_signo; // signal number
@ -54,23 +55,25 @@ struct sigevent
// <fcntl.h> // <fcntl.h>
// //
// .. Win32-only (not specified by POSIX) // Win32 _wopen flags not specified by POSIX:
#define O_TEXT_NP 0x4000 // file mode is text (translated) #define O_TEXT_NP 0x4000 // file mode is text (translated)
#define O_BINARY_NP 0x8000 // file mode is binary (untranslated) #define O_BINARY_NP 0x8000 // file mode is binary (untranslated)
// .. wposix.cpp only (bit values not used by MS _open) // waio flags not specified by POSIX nor implemented by Win32 _wopen:
// do not open a separate AIO-capable handle.
// (this can be used for small files where AIO overhead isn't worthwhile,
// thus speeding up loading and reducing resource usage.)
#define O_NO_AIO_NP 0x20000 #define O_NO_AIO_NP 0x20000
// wposix-specific: do not open a separate AIO-capable handle.
// this is used for small files where AIO overhead isn't worth it,
// thus speeding up loading and reducing resource usage.
// .. not supported by Win32 (bit values not used by MS _open) // POSIX flags not supported by the underlying Win32 _wopen:
#define O_NONBLOCK 0x1000000 #define O_NONBLOCK 0x1000000
// note: we use the sys_wopen interface because there is no // note: we use the sys_wopen interface because there is no
// standardized wide-character open(). // standardized wide-character open().
extern int close(int); extern int close(int);
// //
// <unistd.h> // <unistd.h>
// //
@ -94,8 +97,8 @@ struct aiocb
off_t aio_offset; // File offset. off_t aio_offset; // File offset.
volatile void* aio_buf; // Location of buffer. volatile void* aio_buf; // Location of buffer.
size_t aio_nbytes; // Length of transfer. size_t aio_nbytes; // Length of transfer.
int aio_reqprio; // Request priority offset. int aio_reqprio; // Request priority offset. (unused)
struct sigevent aio_sigevent; // Signal number and value. struct sigevent aio_sigevent; // Signal number and value. (unused)
int aio_lio_opcode; // Operation to be performed. int aio_lio_opcode; // Operation to be performed.
class Impl; class Impl;
@ -105,9 +108,9 @@ struct aiocb
enum enum
{ {
// aio_cancel return // aio_cancel return
AIO_ALLDONE, // None of the requested operations could be canceled since they are already complete. AIO_ALLDONE, // None of the requested operations could be canceled since they are already complete.
AIO_CANCELED, // All requested operations have been canceled. AIO_CANCELED, // All requested operations have been canceled.
AIO_NOTCANCELED, // Some of the requested operations could not be canceled since they are in progress. AIO_NOTCANCELED, // Some of the requested operations could not be canceled since they are in progress.
// lio_listio mode // lio_listio mode
LIO_WAIT, // wait until all I/O is complete LIO_WAIT, // wait until all I/O is complete