diff --git a/src/runtime/c/pgf/db.cxx b/src/runtime/c/pgf/db.cxx index 1bbb77748..ad577f918 100644 --- a/src/runtime/c/pgf/db.cxx +++ b/src/runtime/c/pgf/db.cxx @@ -22,6 +22,21 @@ size_t getpagesize() } #define ftruncate _chsize + +static +int last_error_to_errno() +{ + switch (GetLastError()) { + case ERROR_SUCCESS: + return 0; + case ERROR_OUTOFMEMORY: + return ENOMEM; + case ERROR_HANDLE_DISK_FULL: + return ENOSPC; + default: + return EINVAL; + } +} #endif PGF_INTERNAL __thread unsigned char* current_base __attribute__((tls_model("initial-exec"))) = NULL; @@ -300,6 +315,11 @@ struct PGF_INTERNAL_DECL malloc_state */ ref transient_revisions; ref transient_concr_revisions; + +#ifdef _WIN32 + /* Stores a Reader/Writer lock for Windows */ + LONG lock; +#endif }; PGF_INTERNAL @@ -342,29 +362,62 @@ PgfDB::PgfDB(const char* filepath, int flags, int mode) { this->filepath = strdup(filepath); } + int code = 0; #ifndef _WIN32 #ifndef MREMAP_MAYMOVE if (fd >= 0) { ms = (malloc_state*) mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + code = errno; } else { ms = (malloc_state*) ::malloc(file_size); + code = ENOMEM; } #else int mflags = (fd < 0) ? (MAP_PRIVATE | MAP_ANONYMOUS) : MAP_SHARED; ms = (malloc_state*) mmap(NULL, file_size, PROT_READ | PROT_WRITE, mflags, fd, 0); + code = errno; #endif - if (ms == MAP_FAILED || ms == NULL) { ms = NULL; // mark that ms is not created. ::free((void *) this->filepath); - int code = errno; close(fd); throw pgf_systemerror(code, filepath); } + + try { + rwlock = ipc_new_file_rwlock(this->filepath, &is_first); + } catch (pgf_systemerror e) { +#ifndef MREMAP_MAYMOVE + if (fd < 0) { + ::free(ms); + } else +#endif + munmap(ms,file_size); + + ::free((void *) this->filepath); + close(fd); + throw e; + } #else + char *name; + char buf[256]; + if (fd >= 0) { + BY_HANDLE_FILE_INFORMATION hInfo; + if (!GetFileInformationByHandle((HANDLE) _get_osfhandle(fd), &hInfo)) { + code = last_error_to_errno(); + ::free((void *) this->filepath); + close(fd); + throw pgf_systemerror(code); + } + sprintf(buf, "gf-rwevent-%lx-%lx-%lx", + hInfo.dwVolumeSerialNumber, + hInfo.nFileIndexHigh, + hInfo.nFileIndexLow); + name = buf; + hMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL, PAGE_READWRITE, @@ -375,49 +428,41 @@ PgfDB::PgfDB(const char* filepath, int flags, int mode) { FILE_MAP_WRITE, 0,0,file_size); if (ms == NULL) { + code = last_error_to_errno(); CloseHandle(hMap); hMap = INVALID_HANDLE_VALUE; } } else { + code = last_error_to_errno(); hMap = INVALID_HANDLE_VALUE; ms = NULL; } } else { + code = ENOMEM; + name = NULL; hMap = INVALID_HANDLE_VALUE; ms = (malloc_state*) ::malloc(file_size); } if (ms == NULL) { ::free((void *) this->filepath); - int code = errno; close(fd); throw pgf_systemerror(code, filepath); } -#endif - try { - rwlock = ipc_new_file_rwlock(this->filepath, &is_first); - } catch (pgf_systemerror e) { -#ifndef _WIN32 -#ifndef MREMAP_MAYMOVE - if (fd < 0) { - ::free(ms); - } else -#endif - munmap(ms,file_size); -#else + hRWEvent = CreateEvent(NULL, FALSE, FALSE, name); + if (hRWEvent == NULL) { if (fd < 0) { ::free(ms); } else { UnmapViewOfFile(ms); CloseHandle(hMap); } -#endif - ::free((void *) this->filepath); close(fd); - throw e; + throw pgf_systemerror(code, filepath); } +#endif if (is_new) { init_state(file_size); @@ -437,6 +482,7 @@ PgfDB::PgfDB(const char* filepath, int flags, int mode) { UnmapViewOfFile(ms); CloseHandle(hMap); } + CloseHandle(hRWEvent); #endif ::free((void *) this->filepath); @@ -493,13 +539,16 @@ PgfDB::~PgfDB() { UnmapViewOfFile(ms); CloseHandle(hMap); } + CloseHandle(hRWEvent); #endif } if (fd >= 0) close(fd); +#ifndef _WIN32 ipc_release_file_rwlock(filepath, rwlock); +#endif ::free((void*) filepath); } @@ -550,6 +599,10 @@ void PgfDB::init_state(size_t size) ms->revisions = 0; ms->transient_revisions = 0; ms->transient_concr_revisions = 0; + +#if _WIN32 + ms->lock = 0; +#endif } /* Take a chunk off a bin list. */ @@ -1005,30 +1058,61 @@ object PgfDB::malloc_internal(size_t bytes) size_t new_size = old_size + alloc_size; - if (fd >= 0) { - if (ftruncate(fd, new_size) < 0) - throw pgf_systemerror(errno, filepath); - } - malloc_state* new_ms; #ifndef _WIN32 -// OSX mman and mman-win32 do not implement mremap or MREMAP_MAYMOVE +// OSX do not implement mremap or MREMAP_MAYMOVE #ifndef MREMAP_MAYMOVE if (fd >= 0) { if (munmap(ms, old_size) == -1) throw pgf_systemerror(errno); + ms = NULL; + if (ftruncate(fd, new_size) < 0) + throw pgf_systemerror(errno, filepath); new_ms = (malloc_state*) mmap(0, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (new_ms == MAP_FAILED) + throw pgf_systemerror(errno); } else { new_ms = (malloc_state*) realloc(ms, new_size); + if (new_ms == NULL) + throw pgf_systemerror(ENOMEM); } #else + if (fd >= 0) { + if (ftruncate(fd, new_size) < 0) + throw pgf_systemerror(errno, filepath); + } new_ms = (malloc_state*) mremap(ms, old_size, new_size, MREMAP_MAYMOVE); -#endif if (new_ms == MAP_FAILED) throw pgf_systemerror(errno); +#endif #else + if (fd >= 0) { + UnmapViewOfFile(ms); + CloseHandle(hMap); + ms = NULL; + + hMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), + NULL, + PAGE_READWRITE, + HIWORD(new_size), LOWORD(new_size), + NULL); + if (hMap == NULL) { + hMap = INVALID_HANDLE_VALUE; + throw pgf_systemerror(last_error_to_errno()); + } + + new_ms = (malloc_state*) MapViewOfFile(hMap, + FILE_MAP_WRITE, + 0,0,new_size); + if (new_ms == NULL) + throw pgf_systemerror(last_error_to_errno()); + } else { + new_ms = (malloc_state*) realloc(ms, new_size); + if (new_ms == NULL) + throw pgf_systemerror(ENOMEM); + } #endif ms = new_ms; @@ -1246,16 +1330,142 @@ void PgfDB::sync() if (res != 0) throw pgf_systemerror(errno); #else + if (current_db->fd > 0) { + if (!FlushViewOfFile(ms,size)) { + throw pgf_systemerror(last_error_to_errno()); + } + } +#endif +} + +#ifdef _WIN32 +#define MAX_SPIN 50000 + +__forceinline __int16 ReaderCount(unsigned __int32 lock) +{ + return lock & 0x00007FFF; +} + +__forceinline __int32 SetReaders(unsigned __int32 lock, unsigned __int16 readers) +{ + return (lock & ~0x00007FFF) | readers; +} + +__forceinline __int16 WaitingCount(unsigned __int32 lock) +{ + return (__int16) ((lock & 0x3FFF8000) >> 15); +} + +__forceinline __int32 SetWaiting(unsigned __int32 lock, unsigned __int16 waiting) +{ + return (lock & ~0x3FFF8000) | (waiting << 15); +} + +__forceinline bool Writer(unsigned __int32 lock) +{ + return (lock & 0x40000000) != 0; +} + +__forceinline __int32 SetWriter(unsigned __int32 lock, bool writer) +{ + if(writer) + return lock | 0x40000000; + else + return lock & ~0x40000000; +} + +__forceinline bool AllClear(unsigned __int32 lock) +{ + return (lock & 0x40007FFF) == 0; +} +#endif + +void PgfDB::lock(DB_scope_mode m) +{ +#ifndef _WIN32 + int res = + (m == READER_SCOPE) ? pthread_rwlock_rdlock(rwlock) + : pthread_rwlock_wrlock(rwlock); + if (res != 0) + throw pgf_systemerror(res); +#else + for (int i = 0; ; ++i) { + unsigned __int32 temp = ms->lock; + if (m == READER_SCOPE && !Writer(temp)) { + if (InterlockedCompareExchange(&ms->lock, SetReaders(temp, ReaderCount(temp) + 1), temp) == temp) + return; + else + continue; + } else if (m == WRITER_SCOPE && AllClear(temp)) { + if (InterlockedCompareExchange(&ms->lock, SetWriter(temp, true), temp) == temp) + return; + else + continue; + } else { + if (i < MAX_SPIN) { + YieldProcessor(); + continue; + } + + //The pending write operation is taking too long, so we'll drop to the kernel and wait + if (InterlockedCompareExchange(&ms->lock, SetWaiting(temp, WaitingCount(temp) + 1), temp) != temp) + continue; + + i = 0; //Reset the spincount for the next time + WaitForSingleObject(hRWEvent, INFINITE); + + do + { + temp = ms->lock; + } while (InterlockedCompareExchange(&ms->lock, SetWaiting(temp, WaitingCount(temp) - 1), temp) != temp); + } + } +#endif +} + +void PgfDB::unlock() +{ +#ifndef _WIN32 + pthread_rwlock_unlock(rwlock); +#else + while (true) { + unsigned __int32 temp = ms->lock; + if (ReaderCount(temp) > 0) { + if (ReaderCount(temp) == 1 && WaitingCount(temp) != 0) { + //Note: this isn't nor has to be thread-safe, as the worst a duplicate notification can do + //is cause a waiting to reader to wake, perform a spinlock, then go back to sleep + + //We're the last reader and there's a pending write + //Wake one waiting writer + SetEvent(hRWEvent); + } + + //Decrement reader count + if (InterlockedCompareExchange(&ms->lock, SetReaders(temp, ReaderCount(temp) - 1), temp) == temp) + break; + } else { + while(true) { + temp = ms->lock; + assert(Writer(temp)); + if (WaitingCount(temp) == 0) + break; + + //Note: this is thread-safe (there's guaranteed not to be another EndWrite simultaneously) + //Wake all waiting readers or writers, loop until wake confirmation is received + SetEvent(hRWEvent); + } + + //Decrement writer count + if (InterlockedCompareExchange(&ms->lock, SetWriter(temp, false), temp) == temp) + break; + } + } #endif } DB_scope::DB_scope(PgfDB *db, DB_scope_mode m) { - int res = - (m == READER_SCOPE) ? ipc_rwlock_rdlock(db->rwlock) - : ipc_rwlock_wrlock(db->rwlock); - if (res != 0) - throw pgf_systemerror(res); + db->lock(m); save_db = current_db; current_db = db; @@ -1269,7 +1479,7 @@ DB_scope::~DB_scope() { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wterminate" - ipc_rwlock_unlock(current_db->rwlock); + current_db->unlock(); current_db = save_db; current_base = current_db ? (unsigned char*) current_db->ms diff --git a/src/runtime/c/pgf/db.h b/src/runtime/c/pgf/db.h index 0cee719b5..96184e668 100644 --- a/src/runtime/c/pgf/db.h +++ b/src/runtime/c/pgf/db.h @@ -54,7 +54,11 @@ public: } }; +#ifndef _WIN32 #include "ipc.h" +#endif + +enum DB_scope_mode {READER_SCOPE, WRITER_SCOPE}; class PgfDB { private: @@ -62,10 +66,11 @@ private: const char *filepath; malloc_state* ms; - ipc_rwlock_t *rwlock; - -#ifdef _WIN32 +#ifndef _WIN32 + pthread_rwlock_t *rwlock; +#else HANDLE hMap; + HANDLE hRWEvent; #endif friend class PgfReader; @@ -109,11 +114,12 @@ private: PGF_INTERNAL_DECL object malloc_internal(size_t bytes); PGF_INTERNAL_DECL void free_internal(object o); + void lock(DB_scope_mode m); + void unlock(); + friend class DB_scope; }; -enum DB_scope_mode {READER_SCOPE, WRITER_SCOPE}; - class PGF_INTERNAL_DECL DB_scope { public: DB_scope(PgfDB *db, DB_scope_mode m); diff --git a/src/runtime/c/pgf/ipc.cxx b/src/runtime/c/pgf/ipc.cxx index f09be8741..f9bbb2520 100644 --- a/src/runtime/c/pgf/ipc.cxx +++ b/src/runtime/c/pgf/ipc.cxx @@ -1,11 +1,13 @@ //#define DEBUG_IPC +#ifndef _WIN32 #ifdef DEBUG_IPC #include #include #include #include #define PGF_INTERNAL static + void ipc_error() { perror(NULL); exit(1); @@ -20,7 +22,6 @@ void ipc_toomany() { #define ipc_toomany() throw pgf_error("Too many open grammars") #endif -#ifndef _WIN32 #include #include #include @@ -107,8 +108,8 @@ next: } PGF_INTERNAL -ipc_rwlock_t *ipc_new_file_rwlock(const char* file_path, - bool *is_first) +pthread_rwlock_t *ipc_new_file_rwlock(const char* file_path, + bool *is_first) { if (file_path == NULL) { *is_first = true; @@ -258,7 +259,7 @@ ipc_rwlock_t *ipc_new_file_rwlock(const char* file_path, PGF_INTERNAL void ipc_release_file_rwlock(const char* file_path, - ipc_rwlock_t *rwlock) + pthread_rwlock_t *rwlock) { if (file_path == NULL) { pthread_rwlock_destroy(rwlock); @@ -322,38 +323,6 @@ void ipc_release_file_rwlock(const char* file_path, pthread_mutex_unlock(&locks->mutex); } -#else -PGF_INTERNAL -int ipc_rwlock_rdlock(ipc_rwlock_t *rwlock) -{ - return 0; -} - -PGF_INTERNAL -int ipc_rwlock_wrlock(ipc_rwlock_t *rwlock) -{ - return 0; -} - -PGF_INTERNAL -int ipc_rwlock_unlock(ipc_rwlock_t *rwlock) -{ - return 0; -} - -PGF_INTERNAL -ipc_rwlock_t *ipc_new_file_rwlock(const char* file_path, - bool *is_first) -{ - return NULL; -} - -PGF_INTERNAL -void ipc_release_file_rwlock(const char* file_path, - ipc_rwlock_t *rwlock) -{ -} -#endif #ifdef DEBUG_IPC int main(int argc, char *argv[]) @@ -364,12 +333,13 @@ int main(int argc, char *argv[]) return 1; } - pthread_rwlock_t *rwlock = ipc_new_file_rwlock(argv[2]); + bool is_first; + ipc_rwlock_t *rwlock = ipc_new_file_rwlock(argv[2], &is_first); if (strcmp(argv[1],"r") == 0) { - pthread_rwlock_rdlock(rwlock); + ipc_rwlock_rdlock(rwlock); } else if (strcmp(argv[1],"w") == 0) { - pthread_rwlock_wrlock(rwlock); + ipc_rwlock_wrlock(rwlock); } fputs("> ", stdout); @@ -378,10 +348,11 @@ int main(int argc, char *argv[]) char buf[16]; read(0, buf, sizeof(buf)); - pthread_rwlock_unlock(rwlock); + ipc_rwlock_unlock(rwlock); ipc_release_file_rwlock(argv[2], rwlock); return 0; } #endif +#endif diff --git a/src/runtime/c/pgf/ipc.h b/src/runtime/c/pgf/ipc.h index e6643e138..9d78a5b84 100644 --- a/src/runtime/c/pgf/ipc.h +++ b/src/runtime/c/pgf/ipc.h @@ -2,23 +2,12 @@ #define IPC_H #ifndef _WIN32 -#define ipc_rwlock_t pthread_rwlock_t -#define ipc_rwlock_rdlock pthread_rwlock_rdlock -#define ipc_rwlock_wrlock pthread_rwlock_wrlock -#define ipc_rwlock_unlock pthread_rwlock_unlock -#else -typedef struct ipc_rwlock_t ipc_rwlock_t; -int ipc_rwlock_rdlock(ipc_rwlock_t *rwlock); -int ipc_rwlock_wrlock(ipc_rwlock_t *rwlock); -int ipc_rwlock_unlock(ipc_rwlock_t *rwlock); -#endif - PGF_INTERNAL_DECL -ipc_rwlock_t *ipc_new_file_rwlock(const char* file_path, +pthread_rwlock_t *ipc_new_file_rwlock(const char* file_path, bool *is_first); PGF_INTERNAL_DECL void ipc_release_file_rwlock(const char* file_path, - ipc_rwlock_t *rwlock); - + pthread_rwlock_t *rwlock); +#endif #endif