Skip to content

Add C++ binding thread safety options #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: release/1.8.0-alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/automaticcomponenttoolkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ func printUsageInfo() {
}

func main() {
ACTVersion := "1.8.0-alpha"
ACTVersion := "1.8.1-alpha"
fmt.Fprintln(os.Stdout, "Automatic Component Toolkit v"+ACTVersion)
if len(os.Args) < 2 {
printUsageInfo()
Expand Down
92 changes: 83 additions & 9 deletions Source/buildbindingccpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ func writeDynamicCPPMethodDeclaration(method ComponentDefinitionMethod, w Langua
}

func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, NameSpace string, ClassIdentifier string, ClassName string,
implementationLines []string, isGlobal bool, includeComments bool, doNotThrow bool, useCPPTypes bool, ExplicitLinking bool, forWASM bool) error {
implementationLines []string, isGlobal bool, includeComments bool, doNotThrow bool, useCPPTypes bool, ExplicitLinking bool, forWASM bool, classThreadSafetyOption ThreadSafetyOption) error {

WASMPrefix := ""
WASMCast := ""
Expand Down Expand Up @@ -875,6 +875,12 @@ func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, N
}

w.Writeln(" {")

addThreadSafeCalls := (classThreadSafetyOption == eThreadSafetySoft && requiresInitCall) || classThreadSafetyOption == eThreadSafetyStrict
if addThreadSafeCalls {
w.Writeln(" m_pWrapper->%s(this);", getLockHandleMethodName())
}

w.Writelns(" ", definitionCodeLines)
if requiresInitCall {
w.Writeln(" %s%s(%s)%s;", checkErrorCodeBegin, CMethodName, initCallParameters, checkErrorCodeEnd)
Expand All @@ -888,6 +894,10 @@ func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, N
w.Writelns(" ", implementationLines)
}

if addThreadSafeCalls {
w.Writeln(" m_pWrapper->%s(this);", getUnlockHandleMethodName())
}

if len(returnCodeLines) > 0 {
w.Writeln(" ")
w.Writelns(" ", returnCodeLines)
Expand Down Expand Up @@ -1450,20 +1460,70 @@ func writePolymorphicFactoryImplementation(w LanguageWriter, component Component
w.Writeln("}")
}

func writeCheckErrorImplementation(w LanguageWriter, ErrorMethodName string, ClassIdentifier string, cppBaseClassName string, NameSpace string) {
func writeCheckErrorImplementation(w LanguageWriter, ErrorMethodName string, ClassIdentifier string, cppBaseClassName string, NameSpace string, MultiThreadedEnv bool) {
w.Writeln(" inline void C%sWrapper::CheckError(%s * pBaseClass, %sResult nResult)", ClassIdentifier, cppBaseClassName, NameSpace)
w.Writeln(" {")
w.Writeln(" if (nResult != 0) {")
w.Writeln(" std::string sErrorMessage;")
w.Writeln(" if (pBaseClass != nullptr) {")
w.Writeln(" %s(pBaseClass, sErrorMessage);", ErrorMethodName)
w.Writeln(" %s(pBaseClass);", getUnlockHandleMethodName())
w.Writeln(" }")
w.Writeln(" throw E%sException(nResult, sErrorMessage);", NameSpace)
w.Writeln(" }")
w.Writeln(" }")
w.Writeln(" ")
}

func writeLockHandleImplementation(w LanguageWriter, ClassIdentifier string, cppBaseClassName string) {
w.Writeln(" inline void C%sWrapper::%s(%s * pBaseClass)", ClassIdentifier, getLockHandleMethodName(), cppBaseClassName)
w.Writeln(" {")
w.Writeln(" %s.lock();", getLockMapMutexName())
w.Writeln(" auto it = %s.find(pBaseClass->handle());", getLockMapName())
w.Writeln(" if (it != %s.end()) {", getLockMapName())
w.Writeln(" it->second.first += 1;")
w.Writeln(" }")
w.Writeln(" else {")
w.Writeln(" it = %s.emplace(pBaseClass->handle(), std::make_pair(1, std::make_shared<std::mutex>())).first;", getLockMapName())
w.Writeln(" }")
w.Writeln(" %s.unlock();", getLockMapMutexName())
w.Writeln(" it->second.second->lock();")
w.Writeln(" }")
w.Writeln(" ")
}

func writeUnlockHandleImplementation(w LanguageWriter, ClassIdentifier string, cppBaseClassName string) {
w.Writeln(" inline void C%sWrapper::%s(%s * pBaseClass)", ClassIdentifier, getUnlockHandleMethodName(), cppBaseClassName)
w.Writeln(" {")
w.Writeln(" %s.lock();", getLockMapMutexName())
w.Writeln(" auto it = %s.find(pBaseClass->handle());", getLockMapName())
w.Writeln(" if (it != %s.end()) {", getLockMapName())
w.Writeln(" it->second.second->unlock();")
w.Writeln(" if (--it->second.first == 0) {")
w.Writeln(" %s.erase(pBaseClass->handle());", getLockMapName())
w.Writeln(" }")
w.Writeln(" }")
w.Writeln(" %s.unlock();", getLockMapMutexName())
w.Writeln(" }")
w.Writeln(" ")
}

func getLockMapMutexName() (string) {
return "m_lockMapMutex"
}

func getLockMapName() (string) {
return "m_lockMap"
}

func getLockHandleMethodName() (string) {
return "LockHandle"
}

func getUnlockHandleMethodName() (string) {
return "UnlockHandle"
}

func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace string, BaseName string, ClassIdentifier string, ExplicitLinking bool) error {
useCPPTypes := true

Expand Down Expand Up @@ -1510,6 +1570,10 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s
w.Writeln("#include <array>")
w.Writeln("#include <string>")
w.Writeln("#include <memory>")
if component.isMultiThreadedEnv() {
w.Writeln("#include <mutex>")
w.Writeln("#include <unordered_map>")
}
w.Writeln("#include <vector>")
w.Writeln("#include <exception>")
w.Writeln("")
Expand Down Expand Up @@ -1540,7 +1604,8 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s

w.Writeln(" ")
w.Writeln(" inline void CheckError(%s * pBaseClass, %sResult nResult);", cppBaseClassName, NameSpace)
w.Writeln("")
w.Writeln(" inline void %s(%s * pBaseClass);", getLockHandleMethodName(), cppBaseClassName)
w.Writeln(" inline void %s(%s * pBaseClass);", getUnlockHandleMethodName(), cppBaseClassName)

for j := 0; j < len(global.Methods); j++ {
method := global.Methods[j]
Expand All @@ -1559,6 +1624,11 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s
w.Writeln(" s%sDynamicWrapperTable m_WrapperTable;", NameSpace)
}

if component.isMultiThreadedEnv() {
w.Writeln(" std::mutex %s;", getLockMapMutexName())
w.Writeln(" std::unordered_map<%sHandle, std::pair<int, std::shared_ptr<std::mutex>>> %s;", NameSpace, getLockMapName())
}

if len(component.ImportedComponentDefinitions) > 0 {
w.Writeln(" // Injected Components")
for _, subComponent := range component.ImportedComponentDefinitions {
Expand Down Expand Up @@ -1622,15 +1692,19 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s
implementationLines = append(implementationLines, fmt.Sprintf(" throw E%sException(%s_ERROR_COULDNOTLOADLIBRARY, \"Unknown namespace \" + %s);", NameSpace, strings.ToUpper(NameSpace), sParamName))
}

err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, false)
err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, false, eThreadSafetyNone)
if err != nil {
return err
}

}

w.Writeln("")
writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace)
writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace, component.isMultiThreadedEnv())
w.Writeln("")
writeLockHandleImplementation(w, ClassIdentifier, cppBaseClassName)
w.Writeln("")
writeUnlockHandleImplementation(w, ClassIdentifier, cppBaseClassName)
w.Writeln("")

if ExplicitLinking {
Expand All @@ -1648,7 +1722,7 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s
w.Writeln(" */")
for j := 0; j < len(class.Methods); j++ {
method := class.Methods[j]
err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, false)
err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, false, class.eThreadSafetyOption())
if err != nil {
return err
}
Expand Down Expand Up @@ -2217,14 +2291,14 @@ func buildCppwasmGuestHeader(component ComponentDefinition, w LanguageWriter, Na
implementationLines = append(implementationLines, fmt.Sprintf(" throw E%sException(%s_ERROR_COULDNOTLOADLIBRARY, \"Unknown namespace \" + %s);", NameSpace, strings.ToUpper(NameSpace), sParamName))
}

err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, true)
err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, true, eThreadSafetyNone)
if err != nil {
return err
}
}

w.Writeln("")
writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace)
writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace, false)
w.Writeln("")

for i := 0; i < len(component.Classes); i++ {
Expand All @@ -2236,7 +2310,7 @@ func buildCppwasmGuestHeader(component ComponentDefinition, w LanguageWriter, Na
w.Writeln(" */")
for j := 0; j < len(class.Methods); j++ {
method := class.Methods[j]
err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, true)
err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, true, eThreadSafetyNone)
if err != nil {
return err
}
Expand Down
43 changes: 43 additions & 0 deletions Source/componentdefinition.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ const (
eSpecialMethodBuildinfo = 9
)

type ThreadSafetyOption int
const (
eThreadSafetyNone = 0
eThreadSafetySoft = 1
eThreadSafetyStrict = 2
)

// ComponentDefinitionParam definition of a method parameter used in the component's API
type ComponentDefinitionParam struct {
ComponentDiffableElement
Expand Down Expand Up @@ -90,6 +97,7 @@ type ComponentDefinitionClass struct {
ClassName string `xml:"name,attr"`
ClassDescription string `xml:"description,attr"`
ParentClass string `xml:"parent,attr"`
ThreadSafetyOption string `xml:"threadsafetyoption,attr"`
Methods []ComponentDefinitionMethod `xml:"method"`
}

Expand Down Expand Up @@ -796,6 +804,14 @@ func descriptionIsValid(description string) bool {
return false;
}

func threadSafetyOptionIsValid(threadSafetyOption string) bool {
switch threadSafetyOption {
case "none", "strict", "soft":
return true
}
return false
}

func isScalarType(typeStr string) bool {
switch (typeStr) {
case "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "bool", "single", "double", "pointer":
Expand Down Expand Up @@ -1405,6 +1421,33 @@ func (component *ComponentDefinition) countMaxOutParameters() (uint32) {
return maxOutParameters;
}

func (class * ComponentDefinitionClass ) eThreadSafetyOption() (ThreadSafetyOption) {
switch class.ThreadSafetyOption {
case "strict":
return eThreadSafetyStrict
case "soft":
return eThreadSafetySoft
case "none":
return eThreadSafetyNone
}
return eThreadSafetyNone
}

func (class *ComponentDefinitionClass) isThreadSafe() (bool) {
return class.eThreadSafetyOption() != eThreadSafetyNone
}

func (component *ComponentDefinition) isMultiThreadedEnv() (bool) {
classes := component.Classes
for i := 0; i < len(classes); i++ {
class := classes[i]
if class.isThreadSafe() {
return true
}
}
return false;
}

func (component *ComponentDefinition) classTypeIdMethod() (ComponentDefinitionMethod) {
var method ComponentDefinitionMethod
baseClass := component.baseClass()
Expand Down