/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include
#include "MessagePasserImpl.h"
#include "Messages.h"
#include "lib/timer.h"
#include "lib/rand.h"
#include "lib/posix/posix_filesystem.h"
using namespace AtlasMessage;
MessagePasserImpl::MessagePasserImpl()
: m_Trace(false), m_Semaphore(NULL)
{
int tries = 0;
while (tries++ < 16) // some arbitrary cut-off point to avoid infinite loops
{
static char name[64];
sprintf_s(name, ARRAY_SIZE(name), "/wfg-atlas-msgpass-%d-%d",
(int)rand(1, 1000), (int)(time(0)%1000));
sem_t* sem = sem_open(name, O_CREAT | O_EXCL, 0700, 0);
// This cast should not be necessary, but apparently SEM_FAILED is not
// a value of a pointer type
if (sem == (sem_t*)SEM_FAILED || !sem)
{
int err = errno;
if (err == EEXIST)
{
// Semaphore already exists - try another one
continue;
}
// Otherwise, it's a probably-fatal error
debug_warn(L"sem_open failed");
break;
}
// Succeeded - use this semaphore
m_Semaphore = sem;
m_SemaphoreName = name;
break;
}
if (! m_Semaphore)
{
debug_warn(L"Failed to create semaphore for Atlas - giving up");
// We will probably crash later - maybe we could fall back on sem_init, if this
// ever fails in practice
}
}
MessagePasserImpl::~MessagePasserImpl()
{
if (m_Semaphore)
{
// Clean up
sem_close(m_Semaphore);
sem_unlink(m_SemaphoreName.c_str());
}
}
void MessagePasserImpl::Add(IMessage* msg)
{
ENSURE(msg);
ENSURE(msg->GetType() == IMessage::Message);
if (m_Trace)
debug_printf("%8.3f add message: %s\n", timer_Time(), msg->GetName());
{
CScopeLock lock(m_Mutex);
m_Queue.push(msg);
}
}
IMessage* MessagePasserImpl::Retrieve()
{
// (It should be fairly easy to use a more efficient thread-safe queue,
// since there's only one thread adding items and one thread consuming;
// but it's not worthwhile yet.)
IMessage* msg = NULL;
{
CScopeLock lock(m_Mutex);
if (! m_Queue.empty())
{
msg = m_Queue.front();
m_Queue.pop();
}
}
if (m_Trace && msg)
debug_printf("%8.3f retrieved message: %s\n", timer_Time(), msg->GetName());
return msg;
}
void MessagePasserImpl::Query(QueryMessage* qry, void(* UNUSED(timeoutCallback) )())
{
ENSURE(qry);
ENSURE(qry->GetType() == IMessage::Query);
if (m_Trace)
debug_printf("%8.3f add query: %s\n", timer_Time(), qry->GetName());
// Set the semaphore, so we can block until the query has been handled
qry->m_Semaphore = static_cast(m_Semaphore);
{
CScopeLock lock(m_Mutex);
m_Queue.push(qry);
}
// Wait until the query handler has handled the query and called sem_post:
// The following code was necessary to avoid deadlock, but it still breaks
// in some cases (e.g. when Atlas issues a query before its event loop starts
// running) and doesn't seem to be the simplest possible solution.
// So currently we're trying to not do anything like that at all, and
// just stop the game making windows (which is what seems (from experience) to
// deadlock things) by overriding ah_display_error. Hopefully it'll work like
// that, and the redundant code below/elsewhere can be removed, but it's
// left in here in case it needs to be reinserted in the future to make it
// work.
// (See http://www.wildfiregames.com/forum/index.php?s=&showtopic=10236&view=findpost&p=174617)
// // At least on Win32, it is necessary for the UI thread to run its event
// // loop to avoid deadlocking the system (particularly when the game
// // tries to show a dialog box); so timeoutCallback is called whenever we
// // think it's necessary for that to happen.
//
// #if OS_WIN
// // On Win32, use MsgWaitForMultipleObjects, which waits on the semaphore
// // but is also interrupted by incoming Windows-messages.
// // while (0 != (err = sem_msgwait_np(psem)))
//
// while (0 != (err = sem_wait(psem)))
// #else
// // TODO: On non-Win32, I have no idea whether the same problem exists; but
// // it might do, so call the callback every few seconds just in case it helps.
// struct timespec abs_timeout;
// clock_gettime(CLOCK_REALTIME, &abs_timeout);
// abs_timeout.tv_sec += 2;
// while (0 != (err = sem_timedwait(psem, &abs_timeout)))
// #endif
while (0 != sem_wait(m_Semaphore))
{
// If timed out, call callback and try again
// if (errno == ETIMEDOUT)
// timeoutCallback();
// else
// Keep retrying while EINTR, but other errors are probably fatal
if (errno != EINTR)
{
debug_warn(L"Semaphore wait failed");
return; // (leaks the semaphore)
}
}
// Clean up
qry->m_Semaphore = NULL;
}
bool MessagePasserImpl::IsEmpty()
{
CScopeLock lock(m_Mutex);
return m_Queue.empty();
}
void MessagePasserImpl::SetTrace(bool t)
{
m_Trace = t;
}