Archive

Archive for the ‘Howto's’ Category

howto port unix mmap to win32

November 14th, 2007

I just found a bug which is more than a year old. When reading pages with memory mapped I/O on Windows, the mapping had write-access, and each modification of the mapped page was written to the file. This had no effect so far, but now i had a situation where this caused a test to fail.

On Unix/POSIX platforms, i call mmap() with the flag MAP_PRIVATE, which creates private copies of the pages, and does not modify the file.

fter some searching, i found out how i can mimick this behaviour on Windows:

HANDLE mmaph=CreateFileMapping(fd, 0, PAGE_WRITECOPY, 0, filesize, 0);
... // error checking ommitted
void *page=MapViewOfFile(mmaph, FILE_MAP_COPY, 0, position, size);

The flags PAGE_WRITECOPY and FILE_MAP_COPY use copy-on-write and therefore don’t modify the file, when the page is modified.

hamsterdb 0.4.8 is just around the corner - it features transparent AES encryption and zlib compression! Also, a C++ API and two new samples are in the package. And the mmap bug is fixed, too :)

[edit]
I just noticed that the above code works on Windows 32bit and Windows 64bit, but not on Windows 64bit in Debug mode (an MS Visual Studio project in debug configuration). MapFileOfView returns 5 (ACCESS_DENIED). The release configuration works. This looks like a bug in the Debug runtime libraries. If somebody has a clue what’s going on, please tell me…

[edit #2]
I found the problem; it’s not in the line above, but elsewhere where i calculated the position.  Some nasty casts were involved, which had different results in x64 release mode and x64 debug mode.

chris Coding, Howto's, hamsterdb

win32: Threadsafe initialization of mutexes

August 15th, 2006

The $64-question: is the following function thread safe?


static HANDLE mutex=0;   

void
lock(void)
{
    if (mutex==0)
        mutex=CreateMutex(0, FALSE, 0);
    WaitForSingleObject(mutex, INFINITE);
}

void
unlock(void)
{
    ReleaseMutex(mutex);
} 

void
foo(void)
{
    lock();
    do_something();
    unlock();
} 

foo() is called by several threads. foo creates a mutex, if it wasn’t yet created, locks it, does something and then unlocks the mutex.

The dangerous code is in lock(), when the mutex is created. When two threads call the function simultaneously, and the mutex handle is NULL, both threads create their own mutex, instead of using the same mutex. And then both threads call do_something() in parallel. (This is not just a theoretical case - it has happened in an application i wrote, and it was a reproducable bug.)

To avoid this problem (which actually happened to me a few weeks ago), we have to synchronize the call to CreateMutex(). There are several options, but the fastest should be an atomic “test-and-modify” operation. The win32 SDK offers a function called InterlockedCompareExchangePointer(). It tests, if a pointer has a certain value; if yes, the pointer is exchanged with another value. Here is the new code for lock():


void
lock(void)
{
    if (mutex==0) {
        HANDLE mutex2=CreateMutex(0, FALSE, 0);
        InterlockedCompareExchangePointer(&mutex, mutex2, NULL);
        if (mutex!=mutex2)
             CloseHandle(mutex2);
    }
} 

If the mutex was not yet created, we create a second (temporary) mutex mutex2. InterlockedCompareExchangePointer() replaces mutex with mutex2, but only if mutex is NULL (the third parameter). Otherwise, we release mutex2, to avoid a resource leak.

Since InterlockedCompare-functions use atomic CPU opcodes, the “compare and exchange” will not be interrupted.

On linux this problem doesn’t exist - pthread-mutexes can be initialized statically:

 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 

But that’s impossible for win32 handles, since they are pointers to pointers, and therefore have to be allocated at runtime.

chris Coding, Howto's