1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

* new file lib/UnlockGuard.h — unlock_guard is for unlocking a mutex for the scope time (RAII)

* all lock/unlock and unlock/lock pairs are done by RAII guards now
* fixed two possible crashes at the end of battle when last stack was killed by spell. That should fix #749 and #752.
* fixed a very nasty race condition, eliminating possible deadlock at the start of battle when human hero has tactics
* fixed #422
This commit is contained in:
Michał W. Urbańczyk 2012-02-19 21:03:43 +00:00
parent b35636d5e9
commit e4dc00abac
18 changed files with 239 additions and 131 deletions

View File

@ -1,5 +1,6 @@
#include "StdInc.h"
#include "VCAI.h"
#include "../../lib/UnlockGuard.h"
#define I_AM_ELEMENTAR return CGoal(*this).setisElementar(true)
@ -1093,9 +1094,8 @@ void VCAI::battleEnd(const BattleResult *br)
void VCAI::waitTillFree()
{
cb->getGsMutex().unlock_shared();
auto unlock = vstd::makeUnlockSharedGuard(cb->getGsMutex());
status.waitTillFree();
cb->getGsMutex().lock_shared();
}
void VCAI::validateVisitableObjs()

View File

@ -25,6 +25,7 @@
#ifdef max
#undef max
#endif
#include "lib/UnlockGuard.h"
/*
* CCallback.cpp, part of VCMI engine
@ -230,11 +231,11 @@ void CBattleCallback::sendRequest(const CPack* request)
if(waitTillRealize)
{
std::unique_ptr<vstd::unlock_shared_guard> unlocker; //optional, if flag set
if(unlockGsWhenWaiting)
getGsMutex().unlock_shared();
unlocker = vstd::make_unique<vstd::unlock_shared_guard>(vstd::makeUnlockSharedGuard(getGsMutex()));
cl->waitingRequest.waitWhileTrue();
if(unlockGsWhenWaiting)
getGsMutex().lock_shared();
}
}

View File

@ -289,6 +289,17 @@ namespace vstd
delete ptr;
ptr = NULL;
}
template<typename T>
std::unique_ptr<T> make_unique()
{
return std::unique_ptr<T>(new T());
}
template<typename T, typename Arg1>
std::unique_ptr<T> make_unique(Arg1&& arg1)
{
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1)));
}
}
using vstd::operator-=;

View File

@ -39,7 +39,7 @@ const double M_PI = 3.14159265358979323846;
#define _USE_MATH_DEFINES
#include <cmath>
#endif
#include <boost/format.hpp>
#include "../../lib/UnlockGuard.h"
const time_t CBattleInterface::HOVER_ANIM_DELTA = 1;
@ -367,6 +367,10 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
CBattleInterface::~CBattleInterface()
{
curInt->battleInt = NULL;
givenCommand->cond.notify_all(); //that two lines should make any activeStack waiting thread to finish
if (active) //dirty fix for #485
{
deactivate();
@ -408,7 +412,6 @@ CBattleInterface::~CBattleInterface()
delete g->second;
delete siegeH;
curInt->battleInt = NULL;
//TODO: play AI tracks if battle was during AI turn
//if (!curInt->makingTurn)
@ -1557,6 +1560,7 @@ void CBattleInterface::stackActivated(const CStack * stack) //TODO: check it all
stackToActivate = stack;
waitForAnims();
//if(pendingAnims.size() == 0)
if(stackToActivate) //during waiting stack may have gotten activated through show
activateStack();
}
@ -2053,10 +2057,12 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
void CBattleInterface::battleFinished(const BattleResult& br)
{
bresult = &br;
LOCPLINT->pim->unlock();
animsAreDisplayed.waitUntil(false);
LOCPLINT->pim->lock();
{
auto unlockPim = vstd::makeUnlockGuard(*LOCPLINT->pim);
animsAreDisplayed.waitUntil(false);
}
displayBattleFinished();
activeStack = NULL;
}
void CBattleInterface::displayBattleFinished()
@ -3045,9 +3051,8 @@ void CBattleInterface::startAction(const BattleAction* action)
void CBattleInterface::waitForAnims()
{
LOCPLINT->pim->unlock();
auto unlockPim = vstd::makeUnlockGuard(*LOCPLINT->pim);
animsAreDisplayed.waitWhileTrue();
LOCPLINT->pim->lock();
}
void CBattleInterface::bEndTacticPhase()

View File

@ -30,6 +30,7 @@
#include "CMusicHandler.h"
#include "UIFramework/CGuiHandler.h"
#include "UIFramework/CIntObjectClasses.h"
#include "../lib/UnlockGuard.h"
#ifdef _MSC_VER
#pragma warning (disable : 4355)
@ -1463,9 +1464,8 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
return;
if(h && key.state == SDL_PRESSED)
{
LOCPLINT->pim->unlock();
auto unlockPim = vstd::makeUnlockGuard(*LOCPLINT->pim);
LOCPLINT->cb->moveHero(h,h->pos);
LOCPLINT->pim->lock();
}
}
return;

View File

@ -43,6 +43,7 @@
#include "SDL_syswm.h"
#endif
#include "../lib/CDefObjInfoHandler.h"
#include "../lib/UnlockGuard.h"
#if __MINGW32__
#undef main
@ -543,6 +544,13 @@ void processCommand(const std::string &message)
readed >> fname;
startGameFromFile(fname);
}
else if(cn == "unlock")
{
std::string mxname;
readed >> mxname;
if(mxname == "pim" && LOCPLINT)
LOCPLINT->pim->unlock();
}
else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
@ -709,9 +717,10 @@ static void listenForEvents()
}
//tlog0 << " pushing ";
eventsM.lock();
events.push(ev);
eventsM.unlock();
{
boost::unique_lock<boost::mutex> lock(eventsM);
events.push(ev);
}
//tlog0 << " done\n";
}
}
@ -735,9 +744,8 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
requestChangingResolution();
//allow event handling thread change resolution
eventsM.unlock();
auto unlock = vstd::makeUnlockGuard(eventsM);
while(!setResolution) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
eventsM.lock();
}
else
setResolution = true;

View File

@ -37,6 +37,7 @@
#include "../lib/CGameState.h"
#include "../lib/GameConstants.h"
#include "UIFramework/CGuiHandler.h"
#include "../lib/UnlockGuard.h"
#ifdef min
#undef min
@ -740,7 +741,11 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
//wait till BattleInterface sets its command
boost::unique_lock<boost::mutex> lock(b->givenCommand->mx);
while(!b->givenCommand->data)
{
b->givenCommand->cond.wait(lock);
if(!battleInt) //batle ended while we were waiting for movement (eg. because of spell)
throw boost::thread_interrupted(); //will shut the thread peacefully
}
//tidy up
BattleAction ret = *(b->givenCommand->data);
@ -1158,74 +1163,74 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
}
int i = 1;
//evil...
eventsM.unlock();
pim->unlock();
cb->getGsMutex().unlock_shared();
bool result = false;
bool result = false; //TODO why not set to true anywhere?
{
path.convert(0);
boost::unique_lock<boost::mutex> un(stillMoveHero.mx);
stillMoveHero.data = CONTINUE_MOVE;
//evil...
auto unlockEvents = vstd::makeUnlockGuard(eventsM);
auto unlockPim = vstd::makeUnlockGuard(*pim);
auto unlockGs = vstd::makeUnlockSharedGuard(cb->getGsMutex());
enum TerrainTile::EterrainType currentTerrain = TerrainTile::border; // not init yet
enum TerrainTile::EterrainType newTerrain;
int sh = -1;
const TerrainTile * curTile = cb->getTile(CGHeroInstance::convertPosition(h->pos, false));
for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || curTile->blocked); i--)
{
//changing z coordinate means we're moving through subterranean gate -> it's done automatically upon the visit, so we don't have to request that move here
if(path.nodes[i-1].coord.z != path.nodes[i].coord.z)
continue;
path.convert(0);
boost::unique_lock<boost::mutex> un(stillMoveHero.mx);
stillMoveHero.data = CONTINUE_MOVE;
//stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
if(path.nodes[i-1].turns)
enum TerrainTile::EterrainType currentTerrain = TerrainTile::border; // not init yet
enum TerrainTile::EterrainType newTerrain;
int sh = -1;
const TerrainTile * curTile = cb->getTile(CGHeroInstance::convertPosition(h->pos, false));
for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || curTile->blocked); i--)
{
stillMoveHero.data = STOP_MOVE;
break;
}
//changing z coordinate means we're moving through subterranean gate -> it's done automatically upon the visit, so we don't have to request that move here
if(path.nodes[i-1].coord.z != path.nodes[i].coord.z)
continue;
// Start a new sound for the hero movement or let the existing one carry on.
#if 0
// TODO
if (hero is flying && sh == -1)
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
#endif
{
newTerrain = cb->getTile(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->tertype;
if (newTerrain != currentTerrain)
//stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
if(path.nodes[i-1].turns)
{
CCS->soundh->stopSound(sh);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
currentTerrain = newTerrain;
stillMoveHero.data = STOP_MOVE;
break;
}
// Start a new sound for the hero movement or let the existing one carry on.
#if 0
// TODO
if (hero is flying && sh == -1)
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
#endif
{
newTerrain = cb->getTile(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->tertype;
if (newTerrain != currentTerrain)
{
CCS->soundh->stopSound(sh);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
currentTerrain = newTerrain;
}
}
stillMoveHero.data = WAITING_MOVE;
int3 endpos(path.nodes[i-1].coord.x, path.nodes[i-1].coord.y, h->pos.z);
bool guarded = CGI->mh->map->isInTheMap(cb->guardingCreaturePosition(endpos - int3(1, 0, 0)));
cb->moveHero(h,endpos);
while(stillMoveHero.data != STOP_MOVE && stillMoveHero.data != CONTINUE_MOVE)
stillMoveHero.cond.wait(un);
if (guarded) // Abort movement if a guard was fought.
break;
}
stillMoveHero.data = WAITING_MOVE;
int3 endpos(path.nodes[i-1].coord.x, path.nodes[i-1].coord.y, h->pos.z);
bool guarded = CGI->mh->map->isInTheMap(cb->guardingCreaturePosition(endpos - int3(1, 0, 0)));
cb->moveHero(h,endpos);
while(stillMoveHero.data != STOP_MOVE && stillMoveHero.data != CONTINUE_MOVE)
stillMoveHero.cond.wait(un);
if (guarded) // Abort movement if a guard was fought.
break;
CCS->soundh->stopSound(sh);
}
CCS->soundh->stopSound(sh);
//RAII unlocks
}
cb->getGsMutex().lock_shared();
pim->lock();
eventsM.lock();
if (adventureInt)
{
// (i == 0) means hero went through all the path
@ -1262,9 +1267,8 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
boost::unique_lock<boost::recursive_mutex> un(*pim);
while(dialogs.size())
{
pim->unlock();
auto unlock = vstd::makeUnlockGuard(*pim);
SDL_Delay(20);
pim->lock();
}
CGarrisonWindow *cgw = new CGarrisonWindow(up,down,removableUnits);
cgw->quit->callback += onEnd;
@ -1471,6 +1475,7 @@ void CPlayerInterface::update()
if(terminate_cond.get())
return;
boost::unique_lock<boost::recursive_mutex> un(*pim, boost::adopt_lock); //create lock from owned mutex (defer_lock)
//make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
@ -1485,7 +1490,6 @@ void CPlayerInterface::update()
//in some conditions we may receive calls before selection is initialized - we must ignore them
if(adventureInt && !adventureInt->selection && GH.topInt() == adventureInt)
{
pim->unlock();
return;
}
@ -1505,8 +1509,6 @@ void CPlayerInterface::update()
CCS->curh->draw1();
CSDL_Ext::update(screen);
CCS->curh->draw2();
pim->unlock();
}
int CPlayerInterface::getLastIndex( std::string namePrefix)

View File

@ -123,7 +123,13 @@ void CClient::waitForMoveAndSend(int color)
MakeAction temp_action(ba);
serv->sendPackToServer(temp_action, color);
return;
}HANDLE_EXCEPTION
}
catch(boost::thread_interrupted&)
{
tlog5 << "Wait for move thread was interrupted and no action will be send. Was a battle ended by spell?\n";
return;
}
HANDLE_EXCEPTION
tlog1 << "We should not be here!" << std::endl;
}
@ -182,26 +188,24 @@ void CClient::endGame( bool closeConnection /*= true*/ )
GH.curInt = NULL;
LOCPLINT->terminate_cond.setn(true);
LOCPLINT->pim->lock();
tlog0 << "\n\nEnding current game!" << std::endl;
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = NULL;
tlog0 << "Removed GUI." << std::endl;
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
tlog0 << "\n\nEnding current game!" << std::endl;
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = NULL;
tlog0 << "Removed GUI." << std::endl;
delete CGI->mh;
const_cast<CGameInfo*>(CGI)->mh = NULL;
delete CGI->mh;
const_cast<CGameInfo*>(CGI)->mh = NULL;
const_cast<CGameInfo*>(CGI)->state.dellNull();
tlog0 << "Deleted mapHandler and gameState." << std::endl;
CPlayerInterface * oldInt = LOCPLINT;
LOCPLINT = NULL;
oldInt->pim->unlock();
const_cast<CGameInfo*>(CGI)->state.dellNull();
tlog0 << "Deleted mapHandler and gameState." << std::endl;
LOCPLINT = NULL;
}
while (!playerint.empty())
{
CGameInterface *pint = playerint.begin()->second;
@ -560,9 +564,12 @@ void CClient::battleStarted(const BattleInfo * info)
def = NULL;
if(att || def || gs->scenarioOps->mode == StartInfo::DUEL)
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
new CBattleInterface(info->belligerents[0], info->belligerents[1], info->heroes[0], info->heroes[1],
Rect((settings["video"]["gameRes"]["width"].Float() - 800)/2,
(settings["video"]["gameRes"]["height"].Float() - 600)/2, 800, 600), att, def);
}
if(vstd::contains(battleints,info->sides[0]))
battleints[info->sides[0]]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 0);

View File

@ -3988,8 +3988,7 @@ void CInGameConsole::show(SDL_Surface * to)
std::vector<std::list< std::pair< std::string, int > >::iterator> toDel;
texts_mx.lock();
boost::unique_lock<boost::mutex> lock(texts_mx);
for(std::list< std::pair< std::string, int > >::iterator it = texts.begin(); it != texts.end(); ++it, ++number)
{
SDL_Color green = {0,0xff,0,0};
@ -4009,13 +4008,11 @@ void CInGameConsole::show(SDL_Surface * to)
{
texts.erase(toDel[it]);
}
texts_mx.unlock();
}
void CInGameConsole::print(const std::string &txt)
{
texts_mx.lock();
boost::unique_lock<boost::mutex> lock(texts_mx);
int lineLen = conf.go()->ac.outputLineLength;
if(txt.size() < lineLen)
@ -4042,8 +4039,6 @@ void CInGameConsole::print(const std::string &txt)
}
}
}
texts_mx.unlock();
}
void CInGameConsole::keyPressed (const SDL_KeyboardEvent & key)

View File

@ -73,11 +73,9 @@ public:
void applyOnGS(CGameState *gs, void *pack) const
{
T *ptr = static_cast<T*>(pack);
gs->mx->lock();
// while(!gs->mx->try_lock())
// boost::this_thread::sleep(boost::posix_time::milliseconds(1)); //give other threads time to finish
boost::unique_lock<boost::shared_mutex> lock(*gs->mx);
ptr->applyGs(gs);
gs->mx->unlock();
}
};

View File

@ -59,7 +59,9 @@ ui8 * CLodHandler::giveFile(std::string fname, LodFileType type, int * length)
Entry ourEntry = *en_it;
if(length) *length = ourEntry.realSize;
mutex->lock();
boost::unique_lock<boost::mutex> lock(*mutex);
ui8 * outp;
if (ourEntry.offset<0) //file is in the sprites/ folder; no compression
@ -74,7 +76,6 @@ ui8 * CLodHandler::giveFile(std::string fname, LodFileType type, int * length)
}
else
result = -1;
mutex->unlock();
if(result<0)
{
tlog1<<"Error in file reading: " << myDir << "/" << ourEntry.name << std::endl;
@ -90,7 +91,6 @@ ui8 * CLodHandler::giveFile(std::string fname, LodFileType type, int * length)
LOD.seekg(ourEntry.offset, std::ios::beg);
LOD.read((char*)outp, ourEntry.realSize);
mutex->unlock();
return outp;
}
else //we will decompress file
@ -101,7 +101,6 @@ ui8 * CLodHandler::giveFile(std::string fname, LodFileType type, int * length)
LOD.read((char*)outp, ourEntry.size);
ui8 * decomp = NULL;
infs2(outp, ourEntry.size, ourEntry.realSize, decomp);
mutex->unlock();
delete[] outp;
return decomp;
}

View File

@ -32,18 +32,14 @@ void CThreadHelper::processTasks()
int pom;
while(true)
{
rtinm.lock();
if((pom=currentTask) >= amount)
{
rtinm.unlock();
break;
}
else
{
++currentTask;
rtinm.unlock();
(*tasks)[pom]();
boost::unique_lock<boost::mutex> lock(rtinm);
if((pom = currentTask) >= amount)
break;
else
++currentTask;
}
(*tasks)[pom]();
}
}

View File

@ -27,16 +27,16 @@ template <typename T> struct CondSh
void set(T t)
{
mx.lock();
boost::unique_lock<boost::mutex> lock(mx);
data=t;
mx.unlock();
}
void setn(T t) //set data and notify
{
mx.lock();
data=t;
mx.unlock();
{
boost::unique_lock<boost::mutex> lock(mx);
data=t;
}
cond.notify_all();
};

View File

@ -28,9 +28,10 @@ struct ServerReady
void setToTrueAndNotify()
{
mutex.lock();
ready = true;
mutex.unlock();
{
boost::unique_lock<boost::interprocess::interprocess_mutex> lock(mutex);
ready = true;
}
cond.notify_all();
}
};

83
lib/UnlockGuard.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
namespace vstd
{
namespace detail
{
template<typename Mutex>
class unlock_policy
{
protected:
void unlock(Mutex &m)
{
m.unlock();
}
void lock(Mutex &m)
{
m.lock();
}
};
template<typename Mutex>
class unlock_shared_policy
{
protected:
void unlock(Mutex &m)
{
m.unlock_shared();
}
void lock(Mutex &m)
{
m.lock_shared();
}
};
}
//similar to boost::lock_guard but UNlocks for the scope + assertions
template<typename Mutex, typename LockingPolicy = detail::unlock_policy<Mutex> >
class unlock_guard : LockingPolicy
{
private:
Mutex* m;
explicit unlock_guard(unlock_guard&);
unlock_guard& operator=(unlock_guard&);
public:
explicit unlock_guard(Mutex& m_):
m(&m_)
{
unlock(*m);
}
unlock_guard(unlock_guard &&other)
: m(other.m)
{
other.m = NULL;
}
void release()
{
m = NULL;
}
~unlock_guard()
{
if(m)
lock(*m);
}
};
template<typename Mutex>
unlock_guard<Mutex, detail::unlock_policy<Mutex> > makeUnlockGuard(Mutex &m_)
{
return unlock_guard<Mutex, detail::unlock_policy<Mutex> >(m_);
}
template<typename Mutex>
unlock_guard<Mutex, detail::unlock_shared_policy<Mutex> > makeUnlockSharedGuard(Mutex &m_)
{
return unlock_guard<Mutex, detail::unlock_shared_policy<Mutex> >(m_);
}
typedef unlock_guard<boost::shared_mutex, detail::unlock_shared_policy<boost::shared_mutex> > unlock_shared_guard;
}

View File

@ -305,6 +305,7 @@
<ClInclude Include="ResourceSet.h" />
<ClInclude Include="StartInfo.h" />
<ClInclude Include="StdInc.h" />
<ClInclude Include="UnlockGuard.h" />
<ClInclude Include="VCMI_Lib.h" />
<ClInclude Include="VCMIDirs.h" />
</ItemGroup>

View File

@ -4713,6 +4713,7 @@ bool CGameHandler::dig( const CGHeroInstance *h )
giveHeroNewArtifact(h, VLC->arth->artifacts[2], -1); //give grail
sendAndApply(&iw);
iw.soundID = soundBase::invalid;
iw.text.clear();
iw.text.addTxt(MetaString::ART_DESCR, 2);
sendAndApply(&iw);

View File

@ -33,6 +33,7 @@
#include "../lib/GameConstants.h"
#include <boost/asio.hpp>
#include "../lib/UnlockGuard.h"
std::string NAME_AFFIX = "server";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
@ -97,9 +98,8 @@ void CPregameServer::handleConnection(CConnection *cpc)
if(startingGame)
{
//wait for sending thread to announce start
mx.unlock();
auto unlock = vstd::makeUnlockGuard(mx);
while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
mx.lock();
}
}
}