Better synchronization for multithreading - fixes issue #1435

- The shared mutexes were defined as normal instance variables in a simulator, so the locks didn't work (multiple instances of simulators active). By making them static (which is more appropriate anyway, since we are protecting static data) the synchroniation now works.
- For better performance an own class for multiple read/single write locks was added and used instead of the shared mutexes.
- Made the static wstring_covvert instance local variables in ws2s and s2ws. Otherwise we would also need a mutext to protect it.

- Some other minor changes.
This commit is contained in:
Mike Lischke 2016-12-11 12:52:46 +01:00
parent 1751fac496
commit 4aaa0bfb5a
15 changed files with 134 additions and 57 deletions

View File

@ -1,8 +1,5 @@
# Integrating ANTLR into Development Systems
The Java target is the reference implementation mirrored by other targets. The following pages help you integrate ANTLR into development environments and build systems appropriate for your target language. As of January 2015, we have Java, C#, Python 2, Python 3, and JavaScript targets.
The Java target is the reference implementation mirrored by other targets. The following pages help you integrate ANTLR into development environments and build systems appropriate for your target language. As of December 2016, we have Java, C#, Python 2, Python 3, JavaScript, Go, C++, and Swift targets.
The easiest thing is probably just to use an [ANTLR plug-in](http://www.antlr.org/tools.html) for your favorite development environment.
Java IDE Integration
C# IDE Integration

View File

@ -3,7 +3,7 @@
# Zip it
rm -f antlr4-cpp-runtime-source.zip
zip -r antlr4-cpp-runtime-source.zip "README.md" "cmake" "demo" "runtime" "CMakeLists.txt" "License.txt" "deploy-macos.sh" "deploy-source.sh" "deploy-windows.cmd" "VERSION" \
-X -x "*.DS_Store*" "antlrcpp.xcodeproj/xcuserdata/*" "*Build*" "*DerivedData*" "*.jar" "demo/generated/*" "*.vscode*"
-X -x "*.DS_Store*" "antlrcpp.xcodeproj/xcuserdata/*" "*Build*" "*DerivedData*" "*.jar" "demo/generated/*" "*.vscode*" "runtime/build/*"
# Deploy
#cp antlr4-cpp-runtime-source.zip ~/antlr/sites/website-antlr4/download

View File

@ -1096,7 +1096,7 @@
276E5CE51CDB57AA003FF4B4 /* Arrays.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Arrays.cpp; sourceTree = "<group>"; };
276E5CE61CDB57AA003FF4B4 /* Arrays.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Arrays.h; sourceTree = "<group>"; };
276E5CE71CDB57AA003FF4B4 /* BitSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitSet.h; sourceTree = "<group>"; };
276E5CE81CDB57AA003FF4B4 /* CPPUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPPUtils.cpp; sourceTree = "<group>"; };
276E5CE81CDB57AA003FF4B4 /* CPPUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPPUtils.cpp; sourceTree = "<group>"; wrapsLines = 0; };
276E5CE91CDB57AA003FF4B4 /* CPPUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = CPPUtils.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
276E5CEA1CDB57AA003FF4B4 /* Declarations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Declarations.h; sourceTree = "<group>"; };
276E5CEB1CDB57AA003FF4B4 /* guid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = guid.cpp; sourceTree = "<group>"; };

View File

@ -238,7 +238,7 @@ const atn::ATN& Parser::getATNWithBypassAlts() {
throw UnsupportedOperationException("The current parser does not support an ATN with bypass alternatives.");
}
std::lock_guard<std::recursive_mutex> lck(_mutex);
std::lock_guard<std::mutex> lck(_mutex);
// XXX: using the entire serialized ATN as key into the map is a big resource waste.
// How large can that thing become?
@ -570,7 +570,7 @@ std::vector<std::string> Parser::getRuleInvocationStack(RuleContext *p) {
std::vector<std::string> Parser::getDFAStrings() {
atn::ParserATNSimulator *simulator = getInterpreter<atn::ParserATNSimulator>();
if (!simulator->decisionToDFA.empty()) {
std::lock_guard<std::recursive_mutex> lck(_mutex);
std::lock_guard<std::mutex> lck(_mutex);
std::vector<std::string> s;
for (size_t d = 0; d < simulator->decisionToDFA.size(); d++) {
@ -585,7 +585,7 @@ std::vector<std::string> Parser::getDFAStrings() {
void Parser::dumpDFA() {
atn::ParserATNSimulator *simulator = getInterpreter<atn::ParserATNSimulator>();
if (!simulator->decisionToDFA.empty()) {
std::lock_guard<std::recursive_mutex> lck(_mutex);
std::lock_guard<std::mutex> lck(_mutex);
bool seenOne = false;
for (size_t d = 0; d < simulator->decisionToDFA.size(); d++) {
dfa::DFA &dfa = simulator->decisionToDFA[d];

View File

@ -61,7 +61,7 @@ dfa::Vocabulary const& Recognizer::getVocabulary() const {
std::map<std::string, size_t> Recognizer::getTokenTypeMap() {
const dfa::Vocabulary& vocabulary = getVocabulary();
std::lock_guard<std::recursive_mutex> lck(_mutex);
std::lock_guard<std::mutex> lck(_mutex);
std::map<std::string, size_t> result;
auto iterator = _tokenTypeMapCache.find(&vocabulary);
if (iterator != _tokenTypeMapCache.end()) {
@ -91,7 +91,7 @@ std::map<std::string, size_t> Recognizer::getRuleIndexMap() {
throw "The current recognizer does not provide a list of rule names.";
}
std::lock_guard<std::recursive_mutex> lck(_mutex);
std::lock_guard<std::mutex> lck(_mutex);
std::map<std::string, size_t> result;
auto iterator = _ruleIndexMapCache.find(ruleNames);
if (iterator != _ruleIndexMapCache.end()) {

View File

@ -168,7 +168,7 @@ namespace antlr4 {
atn::ATNSimulator *_interpreter; // Set and deleted in descendants (or the profiler).
// Mutex to manage synchronized access for multithreading.
std::recursive_mutex _mutex;
std::mutex _mutex;
private:
static std::map<const dfa::Vocabulary*, std::map<std::string, size_t>> _tokenTypeMapCache;

View File

@ -42,6 +42,8 @@ using namespace antlr4::dfa;
using namespace antlr4::atn;
const Ref<DFAState> ATNSimulator::ERROR = std::make_shared<DFAState>(INT32_MAX);
antlrcpp::SingleWriteMultipleReadLock ATNSimulator::_stateLock;
antlrcpp::SingleWriteMultipleReadLock ATNSimulator::_edgeLock;
ATNSimulator::ATNSimulator(const ATN &atn, PredictionContextCache &sharedContextCache)
: atn(atn), _sharedContextCache(sharedContextCache) {
@ -56,7 +58,8 @@ PredictionContextCache& ATNSimulator::getSharedContextCache() {
}
Ref<PredictionContext> ATNSimulator::getCachedContext(Ref<PredictionContext> const& context) {
std::lock_guard<std::recursive_mutex> lck(_mutex);
// This function requires a lock as it might modify the cache, however the only path so far where it is called from
// (addDFAState -> optimizeConfigs) already has _stateLock aquired. Adding another lock here would then deadlock.
std::map<Ref<PredictionContext>, Ref<PredictionContext>> visited;
return PredictionContext::getCachedContext(context, _sharedContextCache, visited);
}

View File

@ -33,6 +33,7 @@
#include "atn/ATN.h"
#include "misc/IntervalSet.h"
#include "support/CPPUtils.h"
#include "atn/PredictionContext.h"
namespace antlr4 {
@ -81,8 +82,8 @@ namespace atn {
static ATNState *stateFactory(int type, int ruleIndex);
protected:
// Mutex to manage synchronized access for multithreading.
std::recursive_mutex _mutex;
static antlrcpp::SingleWriteMultipleReadLock _stateLock; // Lock for DFA states.
static antlrcpp::SingleWriteMultipleReadLock _edgeLock; // Lock for the sparse edge map in DFA states.
/// <summary>
/// The context cache maps all PredictionContext objects that are equals()

View File

@ -209,14 +209,19 @@ dfa::DFAState *LexerATNSimulator::getExistingTargetState(dfa::DFAState *s, size_
return nullptr;
}
dfa::DFAState *target = s->edges[t - MIN_DFA_EDGE];
_edgeLock.readLock();
auto iterator = s->edges.find(t - MIN_DFA_EDGE);
#if DEBUG_ATN == 1
if (target != nullptr) {
std::cout << std::string("reuse state ") << s->stateNumber << std::string(" edge to ") << target->stateNumber << std::endl;
if (iterator != s->edges.end()) {
std::cout << std::string("reuse state ") << s->stateNumber << std::string(" edge to ") << iterator->second->stateNumber << std::endl;
}
#endif
_edgeLock.readUnlock();
return target;
if (iterator == s->edges.end())
return nullptr;
return iterator->second;
}
dfa::DFAState *LexerATNSimulator::computeTargetState(CharStream *input, dfa::DFAState *s, size_t t) {
@ -551,8 +556,9 @@ void LexerATNSimulator::addDFAEdge(dfa::DFAState *p, size_t t, dfa::DFAState *q)
return;
}
std::lock_guard<std::recursive_mutex> lck(_mutex);
_edgeLock.writeLock();
p->edges[t - MIN_DFA_EDGE] = q; // connect
_edgeLock.writeUnlock();
}
dfa::DFAState *LexerATNSimulator::addDFAState(ATNConfigSet *configs) {
@ -578,22 +584,23 @@ dfa::DFAState *LexerATNSimulator::addDFAState(ATNConfigSet *configs) {
dfa::DFA &dfa = _decisionToDFA[_mode];
{
std::lock_guard<std::recursive_mutex> lck(_mutex);
if (!dfa.states.empty()) {
auto iterator = dfa.states.find(proposed);
if (iterator != dfa.states.end()) {
delete proposed;
return *iterator;
}
_stateLock.writeLock();
if (!dfa.states.empty()) {
auto iterator = dfa.states.find(proposed);
if (iterator != dfa.states.end()) {
delete proposed;
_stateLock.writeUnlock();
return *iterator;
}
proposed->stateNumber = (int)dfa.states.size();
proposed->configs->setReadonly(true);
dfa.states.insert(proposed);
return proposed;
}
proposed->stateNumber = (int)dfa.states.size();
proposed->configs->setReadonly(true);
dfa.states.insert(proposed);
_stateLock.writeUnlock();
return proposed;
}
dfa::DFA& LexerATNSimulator::getDFA(size_t mode) {

View File

@ -137,7 +137,7 @@ size_t ParserATNSimulator::adaptivePredict(TokenStream *input, size_t decision,
dfa.s0->configs = std::move(s0_closure); // not used for prediction but useful to know start configs anyway
dfa::DFAState *newState = new dfa::DFAState(applyPrecedenceFilter(dfa.s0->configs.get())); /* mem-check: managed by the DFA or deleted below */
s0 = addDFAState(dfa, newState);
dfa.setPrecedenceStartState(parser->getPrecedence(), s0, _mutex);
dfa.setPrecedenceStartState(parser->getPrecedence(), s0, _edgeLock);
if (s0 != newState) {
delete newState; // If there was already a state with this config set we don't need the new one.
}
@ -272,7 +272,9 @@ size_t ParserATNSimulator::execATN(dfa::DFA &dfa, dfa::DFAState *s0, TokenStream
}
dfa::DFAState *ParserATNSimulator::getExistingTargetState(dfa::DFAState *previousD, size_t t) {
_edgeLock.readLock();
auto iterator = previousD->edges.find(t);
_edgeLock.readUnlock();
if (iterator == previousD->edges.end()) {
return nullptr;
}
@ -1182,8 +1184,9 @@ dfa::DFAState *ParserATNSimulator::addDFAEdge(dfa::DFA &dfa, dfa::DFAState *from
}
{
std::lock_guard<std::recursive_mutex> lck(_mutex);
_edgeLock.writeLock();
from->edges[t] = to; // connect
_edgeLock.writeUnlock();
}
#if DEBUG_DFA == 1
@ -1204,26 +1207,28 @@ dfa::DFAState *ParserATNSimulator::addDFAState(dfa::DFA &dfa, dfa::DFAState *D)
return D;
}
{
std::lock_guard<std::recursive_mutex> lck(_mutex);
_stateLock.writeLock();
auto existing = dfa.states.find(D);
if (existing != dfa.states.end()) {
return *existing;
}
auto existing = dfa.states.find(D);
if (existing != dfa.states.end()) {
_stateLock.writeUnlock();
return *existing;
}
D->stateNumber = (int)dfa.states.size();
if (!D->configs->isReadonly()) {
D->configs->optimizeConfigs(this);
D->configs->setReadonly(true);
}
dfa.states.insert(D);
_stateLock.writeUnlock();
D->stateNumber = (int)dfa.states.size();
if (!D->configs->isReadonly()) {
D->configs->optimizeConfigs(this);
D->configs->setReadonly(true);
}
dfa.states.insert(D);
#if DEBUG_DFA == 1
std::cout << "adding new DFA state: " << D << std::endl;
std::cout << "adding new DFA state: " << D << std::endl;
#endif
return D;
}
return D;
}
void ParserATNSimulator::reportAttemptingFullContext(dfa::DFA &dfa, const antlrcpp::BitSet &conflictingAlts,

View File

@ -97,7 +97,7 @@ DFAState* DFA::getPrecedenceStartState(int precedence) const {
return iterator->second;
}
void DFA::setPrecedenceStartState(int precedence, DFAState *startState, std::recursive_mutex &mutex) {
void DFA::setPrecedenceStartState(int precedence, DFAState *startState, SingleWriteMultipleReadLock &lock) {
if (!isPrecedenceDfa()) {
throw IllegalStateException("Only precedence DFAs may contain a precedence start state.");
}
@ -107,8 +107,9 @@ void DFA::setPrecedenceStartState(int precedence, DFAState *startState, std::rec
}
{
std::unique_lock<std::recursive_mutex> lock(mutex);
lock.writeLock();
s0->edges[precedence] = startState;
lock.writeUnlock();
}
}

View File

@ -33,6 +33,10 @@
#include "dfa/DFAState.h"
namespace antlrcpp {
class SingleWriteMultipleReadLock;
}
namespace antlr4 {
namespace dfa {
@ -88,7 +92,7 @@ namespace dfa {
* @throws IllegalStateException if this is not a precedence DFA.
* @see #isPrecedenceDfa()
*/
void setPrecedenceStartState(int precedence, DFAState *startState, std::recursive_mutex &mutex);
void setPrecedenceStartState(int precedence, DFAState *startState, antlrcpp::SingleWriteMultipleReadLock &lock);
/// Return a list of all states in this DFA, ordered by state number.
virtual std::vector<DFAState *> getStates() const;

View File

@ -191,7 +191,7 @@ namespace antlrcpp {
std::string result;
std::size_t nestCount = 0;
next: {
next: {
try {
std::exception_ptr yeptr;
std::swap(eptr, yeptr);
@ -217,12 +217,52 @@ namespace antlrcpp {
goto next;
}
}
result += std::string(nestCount, ')');
return result;
}
//----------------- FinallyAction ------------------------------------------------------------------------------------
FinalAction finally(std::function<void ()> f) {
return FinalAction(f);
}
//----------------- SingleWriteMultipleRead --------------------------------------------------------------------------
void SingleWriteMultipleReadLock::readLock() {
std::unique_lock<std::mutex> lock(_mutex);
while (_waitingWriters != 0)
_readerGate.wait(lock);
++_activeReaders;
lock.unlock();
}
void SingleWriteMultipleReadLock::readUnlock() {
std::unique_lock<std::mutex> lock(_mutex);
--_activeReaders;
lock.unlock();
_writerGate.notify_one();
}
void SingleWriteMultipleReadLock::writeLock() {
std::unique_lock<std::mutex> lock(_mutex);
++_waitingWriters;
while (_activeReaders != 0 || _activeWriters != 0)
_writerGate.wait(lock);
++_activeWriters;
lock.unlock();
}
void SingleWriteMultipleReadLock::writeUnlock() {
std::unique_lock<std::mutex> lock(_mutex);
--_waitingWriters;
--_activeWriters;
if (_waitingWriters > 0)
_writerGate.notify_one();
else
_readerGate.notify_all();
lock.unlock();
}
} // namespace antlrcpp

View File

@ -84,4 +84,21 @@ namespace antlrcpp {
// Get the error text from an exception pointer or the current exception.
std::string what(std::exception_ptr eptr = std::current_exception());
class SingleWriteMultipleReadLock {
public:
void readLock();
void readUnlock();
void writeLock();
void writeUnlock();
private:
std::condition_variable _readerGate;
std::condition_variable _writerGate;
std::mutex _mutex;
size_t _activeReaders = 0;
size_t _waitingWriters = 0;
size_t _activeWriters = 0;
};
} // namespace antlrcpp

View File

@ -44,14 +44,16 @@ void replaceAll(std::string& str, const std::string& from, const std::string& to
}
}
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::string ws2s(const std::wstring &wstr) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::string narrow = converter.to_bytes(wstr);
return narrow;
}
std::wstring s2ws(const std::string &str) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring wide = converter.from_bytes(str);
return wide;
}