Skip to content

Commit

Permalink
Better synchronization for multithreading - fixes issue antlr#1435
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
mike-lischke committed Dec 11, 2016
1 parent 1751fac commit 4aaa0bf
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 57 deletions.
5 changes: 1 addition & 4 deletions doc/IDEs.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion runtime/Cpp/deploy-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion runtime/Cpp/runtime/antlrcpp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>"; };
Expand Down
6 changes: 3 additions & 3 deletions runtime/Cpp/runtime/src/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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++) {
Expand All @@ -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];
Expand Down
4 changes: 2 additions & 2 deletions runtime/Cpp/runtime/src/Recognizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand Down
2 changes: 1 addition & 1 deletion runtime/Cpp/runtime/src/Recognizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion runtime/Cpp/runtime/src/atn/ATNSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
5 changes: 3 additions & 2 deletions runtime/Cpp/runtime/src/atn/ATNSimulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "atn/ATN.h"
#include "misc/IntervalSet.h"
#include "support/CPPUtils.h"
#include "atn/PredictionContext.h"

namespace antlr4 {
Expand Down Expand Up @@ -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()
Expand Down
45 changes: 26 additions & 19 deletions runtime/Cpp/runtime/src/atn/LexerATNSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
39 changes: 22 additions & 17 deletions runtime/Cpp/runtime/src/atn/ParserATNSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions runtime/Cpp/runtime/src/dfa/DFA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand All @@ -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();
}
}

Expand Down
6 changes: 5 additions & 1 deletion runtime/Cpp/runtime/src/dfa/DFA.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@

#include "dfa/DFAState.h"

namespace antlrcpp {
class SingleWriteMultipleReadLock;
}

namespace antlr4 {
namespace dfa {

Expand Down Expand Up @@ -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;
Expand Down
42 changes: 41 additions & 1 deletion runtime/Cpp/runtime/src/support/CPPUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
17 changes: 17 additions & 0 deletions runtime/Cpp/runtime/src/support/CPPUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 4aaa0bf

Please sign in to comment.