Contents lists available atScienceDirect
Science
of
Computer
Programming
www.elsevier.com/locate/scico
Specification
and
verification
of
synchronization
with
condition
variables
Pedro de C. Gomes
a,
∗
,
Dilian Gurov
a,
Marieke Huisman
b,
1,
Cyrille Artho
a aKTHRoyalInstituteofTechnology,Stockholm,SwedenbUniversityofTwente,Enschede,theNetherlands
a
r
t
i
c
l
e
i
n
f
o
a
b
s
t
r
a
c
t
Articlehistory: Received17May2017
Receivedinrevisedform2May2018 Accepted2May2018
Availableonline8May2018 Keywords:
Concurrency Formalverification Java
Conditionvariables
This paper proposes a technique to specify and verify the correctsynchronization of
concurrentprograms withcondition variables. Wedefine correctnessofsynchronization asthe livenessproperty: “everythread synchronizingunderaset ofcondition variables eventually exits the synchronization block”, under the assumption that every such thread eventually reaches its synchronization block. Ourtechnique does not avoid the combinatorial explosion of interleavings of thread behaviours. Instead, we alleviate it by abstracting away all details that are irrelevant to the synchronizationbehaviour of
theprogram,whichistypically significantlysmallerthanitsoverall behaviour.First,we introduceSyncTask, asimple imperative language to specify parallel computations that synchronizeviacondition variables. We consider aSyncTask program tohave acorrect synchronization iff it terminates. Further, to relieve the programmer from the burden of providing specifications in SyncTask, we introduce an economic annotation scheme forJavaprograms toassistthe automatedextraction ofSyncTaskprograms capturing the synchronizationbehaviouroftheunderlyingprogram.Weshowthatevery Javaprogram annotatedaccordingtothescheme(andsatisfyingtheassumptionmentionedabove)hasa correctsynchronizationiffitscorrespondingSyncTaskprogramterminates.Wethenshow howtotransformtheverificationofterminationoftheSyncTaskprogramintoastandard reachabilityproblemoverColoured PetriNetsthatisefficientlysolvablebyexistingPetri Netanalysistools.BoththeSyncTaskprogramextractionandthegenerationofPetriNets areimplementedinour STaVe tool.Weevaluatetheproposedframeworkonanumberof testcases.
©2018ElsevierB.V.Allrightsreserved.
1. Introduction
Conditionvariablesinconcurrentprograms.Conditionvariables (CV)areacommonlyusedsynchronizationmechanismto coor-dinatemultithreaded programs.Threadswait ona CV,meaningthey suspendtheirexecutionuntilanotherthread notifies theCV,causingthewaitingthreadstoresumetheirexecution.Thesignalling isasynchronous:theeffectofthenotification can be delayed.If nothread is waiting on theCV, then thenotification has noeffect. CVs are usedin conjunctionwith
*
Correspondingauthor.E-mailaddresses:pedrodcg@kth.se(P. de C. Gomes),dilian@kth.se(D. Gurov),m.huisman@utwente.nl(M. Huisman),artho@kth.se(C. Artho). 1 SupportedbyERCgrant258405fortheVerCorsproject.
https://doi.org/10.1016/j.scico.2018.05.001 0167-6423/©2018ElsevierB.V.Allrightsreserved.
01 class Utilizer extends Thread { synchronized(lock) { 03 while (!resource_available) { lock.wait(); 05 } } 07 }
class Provider extends Thread { 09 synchronized(lock) { // prepare resource 11 resource_available = true; lock.notify(); 13 } }
Fig. 1. A simple Java program usingwait/notify.
locks;athreadmusthaveacquiredtheassociatedlockfornotifyingorwaitingonaCV,andifnotified,mustreacquirethe lock.
Many widely used programming languages feature condition variables. In Java, for instance, they are provided both nativelyasan object’smonitor [1], i.e., apair ofalockanda CV,andinthe
java.util.concurrent
library,asone-to-many
Condition
objectsassociatedto aLock
object.C/C++ havesimilarmechanismsprovided bythe POSIXthread(Pthread)library,andC++featuresCVsnativelysince2011 [2] asthe
std::condition_variable
class.Themechanism istypicallyemployedwhentheprogressofthreadsdependsonthestateofasharedvariable,toavoidbusy-waitloopsthat pollthestateofthissharedvariable.Example1(ConditionvariablesinJava).Fig.1showsasimpleexamplewithtwothreads:Thefirstthread,Utilizer,wantsto useasharedresource.Theresourceisguardedwithacommonlock(line2)toensurethatonlyonethread,thelockholder, canchangethestateoftheresource.Becausenohigh-levelconstructslike
await(resource_available)
existinJava, theUtilizer threadhastocheckiftheconditionholdsbyusingaconditionalstatement(line3).Iftheconditionisfalse,the Utilizer suspendsitselfbycallingwait
inline4.Thiscallimplicitlyrelinquishesthelock,toallowanotherthreadtoaccess itandmodifytheconditionvariable.Atsomepoint,anotherthreadmaymaketheresourceavailable.Thatthreadthenhas tosignalthestatechangetotheconditionvariable.Inourexample,threadProvider usesthesamelocktoaccesstheshared variable,andcallsnotify
tosignalachangeinline12.Asaresultofthatsignal,oneofthewaitingthreadsiswokenup.Ithasfirsttore-checkthecondition,sinceitmighthave beenre-invalidatedbyanotherthreadinthemeantime.Todothis,thelockis(implicitly)re-acquired.Incaseanotherthread
hasalready consumedthe resource,and
resource_available
is againfalse, thewhile
loop in line3 isre-entered.Otherwise,the waiting threadmayproceed undertheassumption that
resource_available
istrue. Thisassumptionholdsifallaccessestothesharedconditionvariableareprotectedbyacommonlock,i.e.,ifthewholeprogramisdatarace free.
The
notify
methodwakesupanyonethreadthatiswaitingatthetimethenotificationissent;thereisnomechanism toensurethataparticularthreadgetswokenup.Ifmultiplewaitingthreadsmaycheckorusesharedconditionsindifferent ways(for example,by using a function overmultiple shared variables), thenotifying threadshould callnotifyAll
,to ensureeach waiting threadgets wokenuponce andcanre-check thecondition variabletoseeifthe“right” conditionis true.WaitingthreadsmaygetinterruptedinrealJavaprograms,sothey havetoguardanycallto
wait
withatry
/catch
block,tocatchan
InterruptedException
.Furthermore,theJavaSpecification [3,§ 17.2] permits(butdiscourages)JVM implementationstoperformspuriouswake-ups,andreinforcesthecodingpracticeofinvokingwait
insideloopsguarded byalogicalconditionnecessaryforthreadprogress.Weelidethesefunctionalitiesinourpaper.Writingcorrectprogramsusingconditionvariablesischallenging,mainlybecauseofthecomplexity ofreasoningabout asynchronoussignalling. Nevertheless,condition variableshavenotbeen addressedsufficiently withformal techniques,to no smallpart dueto this complexity.For instance,Leino et al. [4] acknowledge that verifyingthe absence of deadlocks whenusingCVsishardbecauseanotificationis“lost”ifnothreadiswaitingonit.Thus,onecannotverifylocallywhether a waitingthread willeventually benotified.Furthermore,the synchronizationconditionscanbe quitecomplex,involving bothcontrol-flow anddata-flowaspectsasarising frommethodcalls;their correctnessthusdependsontheglobalthread composition,i.e.,thetypeandnumberofparallelthreads.Allthesecomplexitiessuggesttheneedforprogrammer-provided annotations toassisttheautomatedanalysis,whichistheapproachwearefollowinghere.
Inthiswork,wepresentaformaltechniqueforspecifyingandverifyingthat“everythreadsynchronizingunderasetof condition variableseventuallyexits thesynchronization”,undertheassumptionthat everysuch threadeventually reaches itssynchronizationblock.Theassumptionitselfisnotaddressedhere,asitdoesnotpertaintocorrectnessofthe synchro-nization,andtherealreadyexisttechniquesfordealingwithsuchproperties(see,e.g., [5]).Notethattheabovecorrectness notionappliestoaone-timesynchronization ona conditionvariableonly;generalizingthenotiontorepeated synchroniza-tions is left for futurework. Tothe best of our knowledge, the present work is the first to address a liveness property involving CVs. As theverification ofsuch properties isundecidable in general, we limit ourtechnique toprograms with boundeddatadomains anda boundednumberofthreads. Still,theverificationproblemissubjecttoacombinatorial ex-plosionofthreadinterleavings.Ourtechniquealleviatesthestate spaceexplosionproblembydelimitingtherelevantaspects ofthesynchronization.
SyncTask. First,we considercorrectnessofsynchronizationinthe contextofasynchronizationspecificationlanguage.Aswe targetarbitraryprogramminglanguagesthatfeaturelocksandconditionvariables,wedonotbaseourapproachonasubset ofanexistinglanguage,butinsteadintroduceSyncTask,asimpleconcurrentprogramminglanguagewhereallcomputations occur inside synchronizedcodeblocks.Wedefine a SyncTaskprogramto haveacorrectsynchronizationiffitterminates. The SyncTask language hasbeen designedto capturecommonpatterns ofCV usage, whileabstracting away from irrele-vant details. Ithas therelevantconstructs forsynchronization, such aslocks,CVs, conditionalstatements,andarithmetic operations. However, it isnon-procedural,datatypes arebounded, andit doesnot allowdynamic thread creation.These restrictionsrenderthestate-spaceofSyncTaskprogramsfinite,andmaketheterminationproblemdecidable.
Verificationofconcurrentprograms.Next,we address the problemofverifying the correctusage ofCVs inreal concurrent programminglanguages.WeshowhowSyncTaskcanbeusedtocapturethesynchronizationofaJavaprogram,providedit isbounded.Object-orientedlanguagessimilartoJava,suchasC++ andC#,can beanalyzedlikewise.Thereisaconsensus in SoftwareEngineering that synchronizationina concurrent program mustbe kept toa minimum,both in thenumber andcomplexityofthesynchronizationactions,andinthenumberofplaceswhereitoccurs [6,7].Thisavoidsthelatencyof blockingthreads,andminimizestheriskoferrors,suchasdead- andlive-locks.Asaconsequence,manyprogramspresenta finite(thougharbitrarilylarge)synchronizationbehaviour.Thatis,thenumberofvariablesinvolvedinthesynchronization, andtheirdatadomainsarebounded.
Implementation.ToassisttheautomatedextractionoffinitesynchronizationbehaviourfromJavaprogramsasSyncTask pro-grams,weintroduceanannotationscheme,whichrequirestheuserto(correctly)annotate,amongothers,theinitializationof newthreads(i.e.,creationof
Thread
objects),andtoprovidetheinitialstateofthevariablesaccessedinsidethe synchro-nizedblocks.WeestablishthatforcorrectlyannotatedJavaprogramswithboundedsynchronizationbehaviour,correctness ofsynchronizationisequivalenttoterminationoftheextractedSyncTaskprogram.As a proof-of-concept ofthealgorithmic solvabilityof theterminationproblemfor SyncTaskprograms, we show how totransformitintoareachabilityproblemonhierarchicalColoured PetriNets2 (CPNs) [8].WedefinehowtoextractCPNs
automatically from SyncTask programs, following a previous technique fromWestergaard [9]. Then, we establish that a SyncTaskprogramterminatesifandonlyif theextractedCPNalwaysreachesdeadmarkings(i.e.,CPNconfigurationswithout successors)wherethetokensrepresentingthethreadsareinauniqueendplace.StandardCPNanalysistoolscanefficiently compute thereachabilitygraphs,andcheckwhethertheterminationconditionholds.Also,incasethattheconditiondoes nothold,aninspectionofthereachabilitygrapheasilyprovidesthecauseofnon-termination.
Evaluation. We implement the extraction ofSyncTask programs fromannotated Java andthe translationof SyncTasks to CPNs as the STaVe tool. We evaluate the tool on two test-cases, by generating CPNs fromannotated Java programs and analyzingthesewithCPNTools [10].Thefirsttest-caseevaluatesthescalabilityofthetoolw. r. t. thesizeofprogramcode thatdoesnotaffectthesynchronizationbehaviouroftheprogram.Thesecondtest-caseevaluatesthescalabilityofthetool w. r. t. the numberofsynchronizingthreads.Theresultsshowtheexpectedexponentialblow-up ofthestate-space,butwe werestillabletoanalyzethesynchronizationofseveraldozensofthreads.
In summary,thisworkmakes thefollowingcontributions: (i) theSyncTasklanguageto modelthesynchronization be-haviour of programs withCVs, (ii) an annotationscheme to aid the extraction ofthe synchronization behaviour of Java programs,(iii) anextractionschemeofSyncTaskmodelsfromannotatedJavaprograms,(iv) areductionofthetermination problemforSyncTaskprograms toa reachabilityproblemonCPNs,(v) animplementationoftheframework by meansof STaVe,and(vi) itsexperimentalevaluation.
Outline.Theremainderofthepaperisorganizedasfollows.Section2introducesSyncTask.Section3describesthemapping from annotated Java to SyncTask, while Section 4 presents the translation into CPNs. Section 5 presents STaVe and its experimentalevaluation.WediscussrelatedworkinSection6.Section7concludesandsuggestsfuturework.
2. SyncTask
SyncTaskabstractsfrommostfeaturesoffull-fledgedprogramminglanguages.Forinstance,itdoesnothaveobjects, pro-cedures,exceptions,etc.However,itfeaturestherelevantaspectsofthreadsynchronization.Wenowdescribethelanguage syntax,types,andsemantics.
2.1. Syntaxandtypes
The SyncTask syntax is presentedin Fig.2. Aprogram has two mainparts: ThreadType*, whichdeclares the different typesofparallelexecutionflows,andMain,whichcontainsthevariabledeclarationsandinitializationsanddefineshowthe threadsarecomposed,i.e.,itstaticallydeclareshowmanythreadsofeachtypearespawned.
2 Thechoiceofformalismhasbeenmainlybasedonthesimplicity ofCPNsasageneralmodelofconcurrency,ratherthanontheexistingsupportfor efficientmodelchecking.Forthelatter,modelcheckingtoolsexploitingparametricityorsymmetriesinthemodelsmayprovemoreefficientinpractice.
SyncTask ::= ThreadType* Main
ThreadType ::= ThreadThreadName{SyncBlock*}
Main ::= main {VarDecl* StartThread*}
StartThread ::= start(Const,ThreadName);
Expr ::= Const|VarName|Expr⊕Expr
| min(VarName)| max(VarName)
VarDecl ::= VarType VarName(Expr*); VarType ::= Bool| Int | Lock | Cond
SyncBlock ::= synchronized (VarName)Block
Block ::= {Stmt*}
Assign ::= VarName=Expr;
Stmt ::= SyncBlock|Block | Assign| skip; | whileExpr Stmt | ifExpr StmtelseStmt | notify(VarName); | notifyAll(VarName); | wait(VarName);
Fig. 2. SyncTask syntax.
01 Thread Producer { synchronized(m_lock){ 03 while(b_els==max(b_els)){ wait(m_cond); 05 } if (b_els<max(b_els)) { 07 b_els=(b_els+1); } else { 09 skip; } 11 notifyAll(m_cond); } 13 } Thread Consumer { 15 synchronized(m_lock){ while((b_els==0)){ 17 wait(m_cond); } 19 if((b_els>0)) { b_els=(b_els-1); 21 } else { skip; 23 } notifyAll(m_cond); 25 } } 27 main { Lock m_lock(); 29 Cond m_cond(m_lock); Int b_els(0,7,1); 31 start(2,Consumer); start(1,Producer); 33 }
Fig. 3. Modelling of synchronization via a shared buffer in SyncTask.
EachThreadType consistsofadjacentSyncBlocks, whicharecriticalsectionsdefinedby acodeblock anda lock.A code
block is defined as a sequence of statements, which may even be another SyncBlock. Notice that this allows nested
SyncBlocks,thusenablingthedefinitionofcomplexsynchronizationschemeswithmorethanonelock.
There are fourprimitive types:booleans (
Bool
), bounded integers (Int
), reentrantlocks (Lock
), andcondition vari-ables (Cond
).ExpressionsareevaluatedasinJava.TheBooleanandintegeroperatorsarethestandardones,whilemax
andmin
returnavariable’sbounds.Operationsbetweenintegerswithdifferentbounds(overloading)areallowed.However,an out-of-boundsassignmentleadstheprogramtoanerrorconfiguration.Conditionvariables aremanipulatedby theunaryoperators
wait
,notify
,andnotifyAll
.Currently, thelanguage providesonlytwocontrolflow constructs:while
andif-else
.Thesesufficefortheillustrationofourtechnique,while theadditionofotherconstructsisstraightforward.The Main block contains the global variable declarations with initializations (VarDecl*), and the thread composi-tion (StartThread*). A variable is defined by declaring its type and name, followed by the initialization arguments. The numberofparametersvariespertype:
Lock
takesnoarguments;Cond
isinitializedwithalockvariable;Bool
takes ei-theratrue
orafalse
literal;Int
takesthreeintegerliteralsasarguments:thelowerandupperbounds,andtheinitial value,whichmustbeinthegivenrange.Finally,start
takesapositivenumberandathreadtype,signifyingthenumber ofthreadsofthattypethatitspawns.Example2 (SyncTaskprogram). The program inFig. 3models synchronization via a sharedbuffer.
Producer
andCon-sumer
representthesynchronizationbehaviour: threadssynchronizeviatheCVm_cond
toaddorremoveelements,and wait ifthebuffer isfull orempty, respectively. Waiting threads arewoken upbynotifyAll
afteran operationis per-formedonthebuffer,andcompeteforthemonitortoresumeexecution.Themain
blockcontainsvariabledeclarationsand initialization.Thelockm_lock
isassociatedtom_cond
.b_els
isaboundedintegerintheinterval[0,7],withinitialvaluesetto
1
,andrepresentsthenumberofelements inthebuffer.OneProducer
andtwoConsumer
threads arespawnedwith
start
.NoticethatthisSyncTaskprogramsimulatestheusageofaJavamonitorsinceitusesapairoflockandCVfor synchro-nization.However,itcouldbemoreefficientlyimplementedwithtwoCVsassociatedtothesamelock:onetonotifywhen thebuffer isfull,andanotherwhenitis empty.Thisalternative approachsimulatestheusage of
Condition
andLock
fromthe
java.util.concurrent
concurrencypackage. 2.2. StructuraloperationalsemanticsWenowdefinethesemanticsofSyncTask,toprovidethemeansforestablishingaformalcorrectnessresult.
Thesemanticdomainsaredefinedasfollows.Booleansarerepresentedasusual.Integervariablesaretriples
Z × Z × Z
, where thefirst two elements are the lower andupperbound, andthe thirdis the currentvalue. Alock o is definedas(
Thread_id× N
+)
∪ ⊥
,whichiseither⊥
ifthelockisfree,orapairoftheidofthethreadholdingthelock,andacounter ofhowmanytimesthelockwasacquiredbythisthread.[s1]a T|(θ, synchronized(o)b,R),μ−→T|(θ, synchronized’(o)b,R),μ[o→ (θ,1)] [s2]b T|(θ, synchronized(o)b,R),μ−→T|(θ, synchronized’(o)b,R),μ[o→ (θ,n+1)] [s3]b T|(θ,b1,R),μ−→T|(θ,b2,X),μ T|(θ, synchronized’(o)b1,R)),μ−→T|(θ, synchronized’(o)b2,X),μ [s4]c T|(θ, synchronized’(o),R)),μ−→T|(θ,,R),μ[o→ (θ,n−1)] [s5]d T|(θ, synchronized’(o),R),μ−→T|(θ,,R),μ[o→ ⊥] [wt]e T|(θ, wait(d),R),μ→T|(θ,, (W,d,n)),μ[lock(d)→ ⊥] [nf1]ef T|(θ, notify(d),R),μ→T|(θ,,R),μ [nf2]eg T|(θ, notify(d),R)|(θ,t, (W,d,n)),μ→T|(θ,,R)|(θ,t, (N,d,n)),μ [na1]ef T|(θ, notifyAll(d),R),μ→T|(θ,,R),μ [na2]egT|(θ, notifyAll(d),R)|TWd ,μ→T|(θ,,R)|{(θ,t, (N,d,n))|(θ,t, (W,d,n))∈TdW},μ [rs]h T|(θ,t, (N,d,n)),μ→T|(θ,t,R),μ[lock(d)→ (θ,n)] aμ(o)= ⊥ bμ(o)= (θ,n) cμ(o)= (θ,n)∧n>1 dμ(o)= (θ,1) eμ(lock(d))= (θ,n) fwaitset(d)= ∅ gwaitset(d)= ∅ hμ(lock(d))= ⊥
Fig. 4. Operational rules for synchronization.
Aconditionvariabled onlymapstoitsassociatedlock(Lock isthedatadomain);hereiswheretheone-to-manyrelation from lockstoCVs is defined. Theauxiliary function lock
(
d)
returns theassociated lockto d.Notethat the setofthreads waitingonaconditionvariableisnot storedontheCVitself;belowwedefinethatthisisstoredatthethreadstate.SyncTaskcontains globalvariables only,andall memoryoperationsaresynchronized.Thus,weassume thememoryto be sequentiallyconsistent [11].Let
μ
representaprogram’s memory.Wewriteμ
(
l)
todenotethevalue ofvariable l,andμ
[
l→
v]
todenotetheupdateof l inμ
withvalue v.A threadstate iseitherrunning (R)ifthe threadis executing,waiting (W ) ifithas suspendedthe executionona CV, ornotified (N)ifanotherthreadhaswokenup thesuspendedthread,butthelockhasnotbeenreacquiredyet.Thestates W and N alsocontaintheCVd that athreadis/waswaiting on,andthenumbern oftimesitmustreacquirethelockto proceedwiththeexecution.Theauxiliaryfunctionwaitset
(
d)
returnstheid’sofallthreadswaitingonaCV d.We represent a thread as
(θ,
t,
X)
, whereθ
denotes its id, t the executing code, and X its thread state. We write T= (θ
i,
ti,
Xi)
|(θ
j,
tj,
Xj)
for a parallel thread composition, withθ
i= θ
j. Also, T|(θ,
t,
X)
denotes a thread composition,assuming that
θ
is notdefinedin T .Forconvenience,we abusesetnotation todenotethe compositionofthreads inthe set;e.g., TdW= {(θ,
t,
(
W,
d,
n))
}
representsthecompositionofallthreadsinthewait setofd.Aprogramconfiguration isa pair(
T,
μ
)
ofthethreads’compositionanditsmemory.Athreadterminatesiftheprogramreachesaconfigurationwhere its code t isempty (); aprogram terminatesifall its threads terminate.We saythat a SyncTaskprogram hasa correct synchronization iffitterminates.
The initialconfigurationisdefinedwiththedeclarationsinMain.Asexpected, thevariableinitializations settheinitial valueof
μ
.Forexample,Int
i(lb,ub,v)
definesanewvariablesuchthatμ
(
i)
= (
lb,
ub,
v),
lb≤
v≤
ub,andLock
o()
initializes a lock
μ
(
o)
= ⊥
. The thread composition is definedby thestart
declarations; e.g.,start(2,t)
adds two threadsoftypet
tothethreadcomposition:(θ,
t,
R)
|(θ
,
t,
R)
.Fig. 4 presents the operational rules, with superscripts a−h denoting conditions. Rule names withprefixes s, wt, nf, na and rs are short for synchronized, wait, notify, notifyAll and resume, respectively. We only define the rules for the synchronizationstatements,astherulesfortheremainingstatementsarestandard [12,§ 3.4-8].
In rule[s1],athreadacquiresa lock,ifavailable,i.e., ifitisnot assignedto anyotherthreadandthecounter iszero. Rule [s2] represents lockreentrancy andincreases the lock counter. Both rules replace
synchronized
witha primed versiontodenotethattheexecutionofsynchronizationblockhasbegun.Rule[s3]appliestothecomputationofstatements inside synchronizedblocks,andrequiresthatthe threadholdsthelock.Rule [s4]decreasesthecounter uponterminating theexecutionofasynchronizedblock,butpreservesthelock.Inrule[s5],athreadfinishestheexecutionofasynchronized block,andrelinquishesthelock.Inthe[wt]rule,athreadchangesitsstateto W ,storesthecounteroftheCV’slock,andreleasesit.Therules[nf1]and [na1]applywhenathreadnotifiesaCVwithanemptywaitset;thebehaviouristhesameasforthe
skip
statement.By rule [nf2],a threadnotifiesaCV, andone threadinitswait setisselectednon-deterministically, andits state ischangedResource annotation:
@resource [ResourceId](classes)
[@object Id [-> Sid]] @value Id [-> Sid] @capacity Id [@defaultval Int] [@defaultcap Int] @predicate(methods)
@inline [@maps Id->@{ Code }@] @code -> @{ Code }@
@operation(methods)
@inline [@maps Id->@{ Code }@] @code -> @{ Code }@
Synchronization annotation:
@syncblock [ThreadId](synchronized blocks)
@resource Id[:ResourceId] -> Sid @lock Id -> Sid
@condvar Id -> Sid @monitor Id -> Sid
Initialization annotation:
@synctask [STid](methods)
@resource Id[:ResourceId] -> Sid @lock Id -> Sid
@condvar Id -> Sid @monitor Id -> Sid @thread [Int:ThreadId]
Fig. 5. Annotation language for Java programs.
to N.Rule[na2]issimilar, butall threadsinthewaitsetare awoken.Bytherule[rs],athreadreacquiresallthelocksit hadrelinquished,changesthestateto R,andresumestheexecutionafterthecontrolpointwhereitinvoked
wait
. 3. FromannotatedJavatoSyncTaskTheannotationprocesssupportedby STaVe reliesontheprogrammer’sknowledgeabouttheintendedsynchronization, andconsistsofprovidinghintstothetooltoautomaticallymapthesynchronizationtoaSyncTaskprogram.Inthissection we present an annotationscheme for writingsuch hints,illustrate SyncTask extraction on an example,define ournotion ofsynchronizationcorrectness forJavaprograms, andcharacterizethenotionasterminationofthecorrespondingSyncTask program.
3.1. AnannotationlanguageandannotationschemeforJava
An annotationin STaVe binds toa specifictype ofJava declaration (e.g.,classes ormethods). Theannotationstarts in a commentblock immediatelyabove adeclaration,withadditionalannotationsinside thedeclaration’sbody.Annotations sharecommonkeywords(thoughwithadifferentsemantics), andoverlapinthedeclarationtypesthey maybindto.The ambiguityisresolvedbythefirstkeyword (calledaswitch)foundinthecommentblock.Commentsthatdonotstartwith akeywordareignored.
Fig.5presentstheannotationlanguage.Argumentsgivenwithinsquarebracketsareoptional,allowingtheprogrammer to(attempt to) leavetheir inference to STaVe, whiletext withinparenthesestellswhich declarationtypestheannotation binds to. The programmer has to provide, by means of annotations, the following threetypesofinformation: resources, synchronizationandinitialization.Below, wedescribetheseinformationtypes,andhow theyshouldbe provided,i.e.,our annotationscheme.
A resource annotates data types of variables that are manipulated by the synchronizationand influence its progress, such as loop guards. The annotationdefines an abstraction ofthe data structure state into a bounded integer, andhow the methods operate onit. Potentially the boundedinteger is a ghostvariable (as in [13]),andin this casewe say that thevariableextends theprogram memory.Forexample,theannotationabstractsalinked listorabufferto itssize.More elaborated, compounddatatypesmaybe annotated, suchasstacks orlistscontaining elements fromaboundeddomain. However,ifathread’sprogressdependsonanelement’svalue,thenthestructurecannotbeabstractedintoasinglebounded integer;instead,werequireaninitializationannotation(seebelow)foreachelementofthedatastructure.
Resourcesbindtoclassesonly.Theswitch
@resource
startsthedeclaration.Incasethataresourcedefinitionisspread acrossseveralclasses(becauseofinheritance),itrequiresacommonResourceId
foreachannotatedclass.The@object
keyword is optionaland instructs STaVe thatthe data structure to analyze is a given variable orfield in theannotated class.
@value
defines whichclass member, orghost variable, storesthe abstract state. Both allow an optionalmappingto an alias Sid, which becomes mandatory incase the resource is definedin more than one class.
@capacity
definestheupperboundfor
@value
.@defaultval
and@defaultcap
definetheresource’sdefault@value
and@capacity
,respectively; these may be overwritten in the initializationannotation (see below). The keyword
@operation
binds to methoddeclarations,andspecifies thatthe methodpotentiallyalterstheresource state.Similarly,@predicate
bindsto methodsandspecifiesthatthemethodreturnsapredicateaboutthestate.There are two ways to extract an annotated method’s behaviour.
@code
tells STaVe not to process the method, but instead toassociate itto thecode enclosed between@{
and}@
,while@inline
tells STaVe totry toinfer the method declaration. The inline is potentially aided by@maps
declarations, which syntactically replaces a Java command (e.g., a methodinvocation)withaSyncTaskcodesnippet.The synchronization annotation defines the observation scope. It binds to
synchronized
blocks andmethods, and01 class Producer extends Thread { Buffer pbuf;
03 Producer(Buffer b){pbuf=b;} public void run() { 05 /*@syncblock @monitor pbuf -> m 07 @resource pbuf:Buffer->b_els*/ synchronized(pbuf) { 09 while (pbuf.full()) pbuf.wait(); 11 pbuf.add(); pbuf.notifyAll(); 13 } } 15 }
class Consumer extends Thread { 17 Buffer cbuf;
Consumer(Buffer b){cbuf=b;} 19 public void run() {
/*@syncblock 21 @monitor cbuf -> m @resource cbuf:Buffer->b_els*/ 23 synchronized(cbuf) { while (cbuf.empty()) 25 cbuf.wait(); cbuf.remove(); 27 cbuf.notifyAll(); } 29 } }
31 /*@resource @capacity cap @object els -> els 33 @value els -> els */
class Buffer {
35 int els; final int cap; /* @operation @inline */ 37 void remove(){if (els>0)els--;}
/* @operation @inline */ 39 void add(){if (els<cap)els++;}
/* @predicate @inline */
41 boolean full(){return els==cap;} /* @predicate @inline */
43 boolean empty(){return els==0;} /*@synctask Buffer
45 @monitor b -> m
@resource b:Buffer->b_els */ 47 static void main(String[] s) {
Buffer b = new Buffer(); 49 b.els = 1; b.cap = 7;
/* @thread */
51 Consumer c1 = new Consumer(b); /* @thread */
53 Consumer c2 = new Consumer(b); /* @thread */
55 Producer p = new Producer(b); c1.start();
57 p.start(); c2.start(); 59 }
}
Fig. 6. Annotated Java program synchronizing via shared buffer.
in casethe annotationis definedin morethan one method orblock. Nested,inner synchronization blocksandmethods are not annotated; allthe requiredinformation hasto beprovided atthe top-levelannotation. Here,
@resource
isnot a switch, and thus has a different meaning. It defines that a local variable Id is a reference to a shared object of an (optional) annotatedresourcetype(ResourceId), andisreferencedbyan aliasSid
acrossother@syncblock
declarations.The keywords
@lock
and@condvar
define which mutex and condition variable object are observed.@monitor
hasthe combinedeffect ofboth keywords foran object’smonitor, i.e.,a pair ofa lockanda condition variable.Similarlyto
@resource
,theserequireamappinganaliasthatiscommontoothersynchronizationdeclarations.Initialization annotationsdefinetheglobalpre-conditionfortheelements involvedinthesynchronization, i.e.,they de-fine initialvaluesforlocks,condition variablesandresourcedeclarations.Theyalsodefine theglobalthreadcomposition, i.e.,howmanyandwhichtypeofthreadsparticipateinthesynchronization.Initializationsbindtomethods,andtheswitch
@synctask
startsthedeclaration.Here,@resource
,@lock
,@condvar
and@monitor
instantiatewithprogram vari-ables the shared aliases defined at@syncblock
. Finally,@thread
defines that the following object corresponds to a spawnedthreadthatsynchronizeswithintheobservedsynchronizationobjects.Theobject’stypeisautomaticallydetected, andmusthavebeenannotatedwithasynchronizationannotation.Alternatively,theannotationcanbefollowedbyathread typeandanumberindicatinghowmanyofthesearespawned,sothatthethreadinstantiationbecomeslessverbose.Some oftheaboveinformation STaVe iscapableofinferringitself; theremaininginformationneeds tobeprovidedby the programmer. STaVe will always indicatewhen theprovided hints areinsufficient. Thisis discussedin moredetail in Section5.
Example3(AnnotatedJavaprogram).TheSyncTaskprograminFig.3wasgeneratedfromtheJavaprograminFig.6.Wenow discusshowtheannotationsdelimittheexpectedsynchronization,indirectlyillustratingtheSyncTaskextraction.
The
@syncblock
annotations (lines 5/20) add the followingsynchronized
blocks to the observed synchroniza-tionbehaviour, andits arguments@monitor
and@resource
(lines 6/21and7/22,respectively)maplocalreferencesto shared aliases. The@resource
annotation (line 31) starts the definitionof a resource type.@value
,@object
,@ca-pacity
(lines 31/32/33) define how the abstract state isrepresented by a bounded integer. Here, to keep the running example simple,the abstractstatehasbeenchosen tobe equaltotheboundedintegerels
.However, inatypical buffer implementationtheabstractionwouldbefromthebuffercontenttoaghostvariablecontainingthenumberofelementsin thebuffer.The@operation
(lines36/38)and@predicate
(lines40/42)annotationsdefinehowthemethodsoperateon thestate.NoticethattheannotatedmethodshavebeeninlinedinFig.3,i.e.,add
isinlinedinlines6–10.The@synctask
annotation above
main
starts thedeclarationoflocks,CVsandresources, and@thread
annotations addthe underneath objectstotheglobalthreadcomposition.Theannotationsprovidedinthisexampleweresufficientfor STaVe toinferthatdifferentvariablesthatarespreadalong thecodeactuallypointtotherelevantartifacts.Furthermore, STaVe waseitherabletoinferorinlinetheotherinformation itneeded(methods’controlflow,initializations,etc.),ortheinformationwasprovidedintheannotations.
static void main(String[] s) { Buffer b = new Buffer(); b.els = 1; b.cap = 7; Consumer c1; Consumer c2; Producer p; while (true) { c1 = new Consumer(b); c2 = new Consumer(b); p = new Producer(b);
c1.start(); p.start(); c2.start(); c1.join(); c2.join(); p.join(); b.els = 1; b.cap = 7;
} }
Fig. 7. Example of support for sessions.
Annotationscanbeunderstoodasprograminvariants intheusualstaticanalysissense.Thatis,ascontrol-pointinvariants whichholdeverytimeprogramexecutionisatagivencontrolpoint(atwhichtheannotationisplaced).Aprogramisthen considered to be correctlyannotated wheneverthe provided annotations hold. Although outsidethe scope ofthe present work,theannotationscanpotentiallybechecked,orpartiallygenerated,withexistingstaticanalysistechniques,suchas [14,
4].We shallhenceforthassumethat theprogrammer hascorrectlyannotatedtheprogram.Furthermore,we shallassume thememorymodelofsynchronizedactionsinaJavaprogramtobesequentiallyconsistent.
3.2. Synchronizationcorrectness
Thesynchronizationpropertyofinteresthereisthat“everythreadsynchronizingunderasetofconditionvariableseventually exitsthesynchronization”. We work under the assumption that every such thread eventually reaches its synchronization block. Thereexisttechniques [5] forcheckingthelivenesspropertythata giventhreadeventuallyreachesagivencontrol point;checkingvalidityoftheaboveassumptionisthereforeoutofthescopeofthepresentwork.
Thefollowingdefinitionofcorrectsynchronizationappliestoaone-timesynchronization ofaJavaprogram.However,the notioneasilygeneralizestoprogramsthatoperateinsessions byrepeatedlyre-spawningthesynchronizingthreads(i.e.,the one-timesynchronizationscheme),providedthat thesynchronizationvariablesareresetatthestartofeachsession.Fig.7
illustratesthisnotionwithamodifiedversionofthe
main
methodfromExample3.Weshouldstressthatweusethetermcorrectness heretoreferexclusivelytothepropertymentionedabove;wedonot referwithittootherundesirablesynchronizationphenomena,suchasdataracefreedom.
Definition1 (Synchronizationcorrectness). Let
P
be a Java programwith a one-timesynchronization, whereevery thread eventuallyreachestheentrypointofitssynchronizationblock. WesaythatP
hasacorrectsynchronization iffeverythread eventuallyreachestheexitpointoftheblock.WenowconnectsynchronizationschemesofcorrectlyannotatedJavaprogramswithSyncTaskprograms.
Theorem1(Characterization).AcorrectlyannotatedJavaprogramhasacorrectsynchronizationiffitscorrespondingSyncTask termi-nates.
Proofsketch. To provethe result, we define abinary relation R betweenthe configurations ofthe Javaprogram andits correspondingSyncTaskprogram,andshowittobeaweakbisimulation (see [15])forasuitablychosennotionofobservable andsilent transitionsbetweenconfigurations. Oneaspectofthe choiceisthat theannotations guaranteethatthe control flow oftheoriginal programispreserved, andthus,noinfinitesilent behavioursare possiblewithin thesynchronization. Therefore,aweakbisimulationrelationisadequateandsufficienttoestablishthedesiredprogressproperty.Werefertothe accompanyingtechnicalreport [16] forthefullformalizationandforthemostinterestingproofcases,namelythe
notify
and
wait
instructions.TheJavaannotations defineabidirectionalmappingbetween(someof)theJavaprogram variablesandghost variables andthecorrespondingboundedvariablesinSyncTask.Thus,wedefine R torelateconfigurationsthatagreeontheircommon variables. Similarly, we define the set of observabletransitions as the ones that update common variables, andtreat all remaining transitionsassilent.We arguethat R isa weakbisimulation inthestandard fashion:We establishthat (i) the initialvaluesofthecommonvariablesarethesameforbothprograms,and(ii) assumingthatobservedvariablesinaJava programare onlyupdated insideannotatedsynchronizedblocks,we establishthatanyoperation thatupdatesa common variablehasthesameeffectonitinbothprograms.
Toprove(i)itsufficestoshowthattheinitialvaluesintheJavaprogramarethesameastheonesprovidedinthe ini-tializationannotation,asdescribedinSection3.1.Theproofof(ii)requirestoshowthatupdatestoacommonvariableyield thesameresultinbothprograms.ThisgoesbycaseanalysisontheJavainstructionsset.Eachcaseshowsthatforany con-figurationpairof R,theoperationalrulesforthegivenJavainstructionandforthecorrespondingSyncTaskinstructionlead toapairofconfigurationsthatagainagreeonthecommonvariables.AsthesemanticsofSyncTaskpresentedinSection2
Fig. 8. Top-level component and condition variables operations.
4. Verificationofsynchronizationcorrectness
Inthissection weshow howterminationofSyncTaskprogramscanbe reducedtoareachabilityproblemonColoured PetriNets(CPN).
4.1. SyncTaskprogramsasColoured PetriNets
Varioustechniquesexisttoproveterminationofconcurrentsystems.ForSyncTask,itisessential thatsuchatechnique efficientlyencodestheconcurrentthreadinterleaving,theprogram’scontrolflow,synchronizationprimitives,andbasicdata manipulation.Here,wehavechosentoreducetheproblemofterminationofSyncTaskprograms toareachabilityproblem on hierarchical CPNs extracted fromthe program. CPNs are supported by analysis toolssuch asCPN Tools, andallow a naturaltranslationofcommonlanguageconstructs intoCPNcomponents.Forthiswe reuseresultsfromWestergaard [9], andonly hadtomodelthe constructsinvolvingCVs that wepresentbelow. Weassume some familiaritywithCPNs,and referthereaderto [8] foradetailedexposition.
The colour set THREAD associates a colour toeach
Thread
type declaration,anda thread isrepresentedby a token with acolour from theset.Some components areparametrized by THREAD,meaning thatthey declare transitions,arcs, orplacesforeachthreadtype.Forillustrationpurposes,wepresenttheparametrizedcomponentsinan examplescenario withthreethreadtypes:blue (B),red (R),andyellow (Y).TheproductionrulesinFig.2aremappedintohierarchicalCPNcomponents,wheresubstitutetransitions (STs;depictedas doublyoutlinedrectangles)representthenon-terminalsontheright-hand side.Fig.8a showsthecomponentforthestart symbol SyncTask.The Start placecontainsallthreadtokens intheinitial configuration,connectedbyarcs(onepercolour)
totheSTsdenotingthethreadtypes,and End,whichcollectstheterminatedthreadtokens.Italsocontainstheplacesthat representglobalvariables.
Fig.8b showsthemodellingof
wait
.The transitionwait
cond
produces two tokens:one intotheplace modelling theCV, andoneintothe placemodellingthelock, representingits release.Theother transitionmodels anotifiedthread reacquiringthe lock, and resumingthe execution. Fig. 8cshowsthe modelling ofnotify
. TheEmpty_cond
transition isenablediftheCVisempty, andtheother transitions,withone placepercolour,modelthenon-deterministicchoiceof whichthreadtonotify.ThecomponentfornotifyAll
(notshown)issimilar.The initialization inMain declares the initialset oftokens forthe placesrepresenting variables, andthenumber and colours ofthread tokens. A
Lock
createsa place containing a single token; it beingempty representsthat some thread holds the lock. The colour set CPOINT represents the control points ofwait
statements. ACondition
variable gives rise toan emptyplace representingthewaiting set,withcolour set CONDITION. Here,colours are pairsof THREAD and CPOINT.Bothdataarenecessarytoroutecorrectlynotifiedthreadstothecorrectplacewheretheyresumeexecution. 4.2. SyncTaskterminationasCPNreachabilityWe nowenunciate the resultthat reducesterminationof a SyncTaskprogram to areachability problemon its corre-spondingCPN.
Theorem2(SyncTasktermination).ASyncTaskprogramterminatesiffitscorrespondingCPNunavoidablyreachesadeadconfiguration inwhichtheEndplacehasthesamemarkingastheStartplaceintheinitialconfiguration.
Proofsketch. A CPNdeclares a placeforeach SyncTask variable.Moreover, there isa clearcorrespondencebetweenthe operationalsemantics ofaSyncTaskconstructanditscorresponding CPNcomponent. Itcan beshownbymeans ofweak bisimulation that every configuration ofa SyncTask program is matched by a unique sequence ofconsecutive CPN con-figurations. Therefore, if the End place in a dead configuration has the same marking as the Start place in the initial configuration,theneverythreadintheSyncTaskprogramterminatesitsexecution,forevery possiblescheduling(notethat thenon-deterministicthreadschedulerissimulatedbythenon-deterministicfiringoftransitions).
2
CPN terminationitself can be verified algorithmically by computing the reachabilitygraph of thegenerated CPN and checkingthat:(i) thegraphhasnocycles,and(ii) theonlyreachabledeadconfigurationsaretheoneswherethemarking inthe End placeisthesameasthemarkinginthe Start placeintheinitialconfiguration.
5. The STAVEtool
Inthissectionwepresenttheimplementationofourtool,discussitscapabilitiestoinfersomeoftheinformationneeded forthetranslationtoSyncTask,andpresenttheresultsofourexperimentalevaluation.
5.1. Implementation
We have implemented the parsingof annotated Javaprograms to generateSyncTask programs, and the extractionof hierarchicalCPNsfromSyncTask,asthe STaVe tool.IthasbeenwritteninJava,andisavailableat [17].
STaVe processes the annotations in an intricate scheme. It takesthe annotated Java program as input, and uses the JavaParserlibrarytogeneratetheAST.ThenitconvertstheJavaParser’s ASTintotheoneoftheOpenJDKcompiler,totake advantageofits symboltablequerying,type checkingandcodeoptimization. WehaveadoptedJavaParser fortheparsing becauseitassociatesthecommentsper-ASTnode,whileOpenJDK’s parserdiscardsannotationsofafinergranularitythan methods. Forinstance, theuse of JavaParser allows theannotation of
synchronized
blocks. Next, STaVe traversesthe Java AST three times to extract the SyncTask program’s AST. The first pass processes resource annotations, and extracts informationabout howthreads operate onshared variables. Thesecond pass processes synchronizationannotations,and usestheinformationfromthepreviouspasstogeneratethecontrolflowstructureofthethreads.Thethirdpassprocesses initializationannotations,andchecksifthedeclaredvariablesandthreadtypeshavebeenproperlyparsedintheprevious steps. After the SyncTask AST is created, it is traversed following the mapping described in Section 4 to generate the correspondingCPN.Twopartsof STaVe turnedouttobeusefulinitself,i.e.,usefulforotherprojects.ThefirstisJavaParser2JCTree,3alibrary thattranslatesJavaParserASTstoOpenJDKASTs.Thesecondislibcpntools,4 alibrarythatgenerateshierarchicalCPNsinthe
CPNTools’sXML-basedfileformat.
3 Availableathttps://github.com/pcgomes/javaparser2jctree. 4 Availableathttps://github.com/pcgomes/libcpntools.
5.2. Staticanalysis
Some oftheinformationaboutthesynchronizationbehaviouroftheanalyzedprogram,whichisneededforthe extrac-tionoftheSyncTaskprogram,canbededucedby STaVe itself.Basically,thisistheinformationwhichtheJavacompilercan deduce.Thus,thetoolcanautomatically(theexamplesinparenthesesrefertoFig.6):
•
deduceinitializationinvolvingconstants:thenumberofthreads,aresourcecapacity,etc.(lines50–55);•
deducesimplecontrol-flowofthesynchronizationblocks,includingthecaseofmethodinvocationswithoutrecursion;•
nameaSyncTaskconstructfromitsoriginatingJavacounterpart,asforinstance,anannotatedsynchronizedblock will benamedaftertheJavaclassthatdefinesit(classConsumer
);•
assignautomaticallyalabeltovariableswiththesamenameandtype,evenifdeclaredandusedindistinctfilesand/or methods;•
inferinformationthatinvolvestheclasshierarchy,asforinstance,itisabletounderstanda“resource”thathassome methodsdefinedinaparentclass,whileothermethodsintheannotatedclass.Our tool could be extended with severaladditional, specialized staticanalyses that wouldautomate theinference of varioustypesofinformation,neededforthetranslationtoSyncTask.Themaincandidatewouldbeapointeranalysis,which wouldinferwhentwovariablesindistinctpartsofthecodeinvariablypointtothesameobject.Currentlythetoolrequires the userto “tie”such variables usinglabels. Thatis,the usermanually assigns a globallabelto aJava variable,andthe labelwill becomethe nameoftherespectiveSyncTask variable.Forinstance,lines 6,21and45inFig.6 definethat the Javavariablesnamed
buffer
,buffer
andb
intheirrespectivemethods,actuallyreferencethesameobjectm
(whichisa labeltorefertothatobject).5.3. Experimentalevaluation
Wenowdescribetheexperimentalevaluationofourframework.ThisincludestheprocessofannotatingJavaprograms, extractionofthecorrespondingCPNs,andtheanalysisofthenetsusingCPNTools.
Our first test case evaluates the usage of STaVe and the annotation process in a real-world program. For this, we annotatedPIPE [18] (version4.3.2),aratherlargeCPNanalysistool writteninJava.Itcontains asingle (andsimple) syn-chronization schemewithtwothreads usingCVs:whenthereisanewconnectionattemptfromaremoteclient,athread establishestheconnectionandthennotifiesthesharedCV;theotherthreadwriteslogstotheclient,andwaitsontheCVif thesocketisnotready.ThistestcaseillustratesthatsynchronizationinvolvingCVsistypicallysimpleandbounded.Italso exemplifiesasessionsynchronizationsincetheonlyvariable,abooleanthatflagsifthesocketisready,hasthesamevalue (false)atthestartofeachsession.Westress,however,that STaVe analyzesitasbeingaone-timesynchronization.Manually annotatingtheprogramtookjustafewminutes,oncethesynchronizationschemewasunderstood.TheCPNextractiontime wasnegligible,andtheverificationprocesstookjustafewmillisecondstoestablishcorrectness.
Oursecondtestcaseevaluatesthescalabilityofourapproachusing STaVe andstate-spaceexploration(withCPNTools) w. r. t.thenumberofthreads.WetookExample3,andinstantiateditwithavaryingnumberofthreads,buffercapacity,and initialvalue.
Asareference,weusedJavaPathfindertoanalyzethesameprogram.JavaPathfinder [19] (JPF)isanobviouschoicefor analyzingJavaprogramswithwait/notify,asitcandetectthesametypesofdeadlock(lackofprogress)that STaVe analyzes. JPFsupportsthe fullbytecodeinstructionsetandcananalyzethefull statespaceofconcurrentapplicationsthat haveno nativemethods (methodsthat executemachinecodelibrariesonthe hostsystem).Fornativemethods,modelclassescan beprovidedtoreplacethemwithequivalentcodeinJava,butthisisoftenacomplextask [20].
When using STaVe, its back-end, CPN Tools, generated the state graph, which we later queried using its ML-based API [21].We remark that, differentfromthe preliminary version ofthispaper [22], herewe take into account thetime ofamandatoryinitializationphasecalledEntertheStateSpace.Asexpected,thisleadstohigherverificationtimes.Asbefore, wecollectourstatisticsbyconsideringthestate-spacegeneration,computationofthestronglyconnectedcomponents,and verificationofthethreeterminationconditions.Namely:whetherthereisatleastonedeadconfiguration;whether,forall dead configurations,the End placehasthe samemarkingasthe Start place inthe initialconfiguration; andwhetherthe numberofstronglyconnectedcomponentsisequaltothesizeofthestategraph,implyingtheabsenceofcycles.
The experimentswere executedinaLinuxmachine with16 GB ofRAMandaquad-coreInteli5CPUof1.30 GHz.The JPF experimentswere executed with version 8.0rev 32,on Java 1.8.0_121.We gave JPF4 GB ofheap space(an amount that wasneverfullyused)andrantheexperimentswithoutatimeoutofonehour.Inadditiontotheexecutiontimes,JPF showsthenumberofexploredstatesandthenumberofexecutedbytecodeinstructions.TheCPNToolsexperimentswere performedwithversion4.0.1 inaWindows 7virtualmachine runningunderVirtualBox version5.1.32 with8 GB ofRAM and2processors.
Table 1
StatisticsforProducer/Consumer.Forgivenconfigurations,thenumberofprogramstatesandtheanalysistimeisshownforbothtools.ForJavaPathfinder, wealsoshowthenumberofbytecodeinstructionsexecutedduringthewholeanalysis.
Problem size Analysis results
Threads Buffer Java Pathfinder STaVe/CPN tools
producers consumers capacity elements terminates? # states # instr. time [mm:ss] # states time [mm:ss]
1 2 1 1 yes 1,466 43,603 0:01 42 0:05 1 2 2 0 no 22 3,878 0:00 43 0:05 2 2 1 0 yes 10,533 294,823 0:03 91 0:05 3 3 1 0 yes 613,052 21,035,480 2:12 283 0:05 4 3 1 0 yes 4,864,766 187,705,560 20:08 448 0:05 4 3 1 1 no 64 4,754 0:00 440 0:06
6 5 1 0 yes timeout after one hour 2,152 0:07
6 5 1 1 no 122 5,740 0:00 2,131 0:05
6 5 5 1 yes timeout after one hour 950 0:06
6 5 5 4 yes timeout after one hour 968 0:05
7 1 5 0 no 74 4,946 0:00 157 0:05
7 6 1 1 no 154 6,260 0:00 3,938 0:06
7 6 7 1 yes timeout after one hour 1,395 0:06
11 11 1 0 yes timeout after one hour 29,143 0:18
11 9 7 6 no 172 7,564 0:00 6,573 0:07
14 13 1 1 no 434 10,404 0:00 64,075 0:51
14 13 7 1 yes timeout after one hour 29,573 0:16
16 21 5 5 yes timeout after one hour 164,921 3:48
17 16 16 16 no 131 10,077 0:00 24,833 0:13
18 18 1 1 yes timeout after one hour 197,563 5:25
18 18 5 1 yes timeout after one hour 133,824 2:34
20 18 2 1 no 704 14,120 0:00 217,702 6:09
22 21 16 16 no 364 12,590 0:00 84,603 0:51
26 24 25 24 no 199 13,615 0:00 78,191 0:39
Table 1presents the practical evaluationfor a numberof initial configurations withvarying number ofthreads (Pro-ducer andConsumer),buffercapacity andposition5 (elements).Columnterminates? showsifaninitialprogramconfiguration has correctsynchronizationw. r. t. Definition 1.For thecases where JPFtimedout, the presented resultscome fromthe STaVe/CPNtoolsanalysisonly.As expected,the otherresultsmatchandcomefromboth analysis.The termstate replaces CPNconfiguration at STaVe statisticstoavoidconfusionwiththeconceptshowninProblemsize,andtofacilitatethe compar-isonbetweenthestate-spacesizes.Timespresentedas0:00meanlessthanonesecond.
We observe an expectedcorrelation between the numberof tokens representingthreads, the size of the state space, andthe verificationtime. Lessexpected foruswas the observed influenceofthe buffer capacities andinitial states.We conjecturethat theinitial configurationswhichmodelhighcontention, i.e.,manythreads waitingon CVs,inducea larger statespace.ThiseffectisparticularlystrongwithJavaPathfinder,whichhastoexecuteallrelevantconfigurationsexplicitly asprogram code. The experiments alsoshow how termination dependson the threadcomposition andthe initial state. Hence,asinglechangeinanyparametermayaffecttheverificationresult.
5.4. StatespaceexplosionwithJavaPathfinder
Toconfirmthetrendofsharplyexplodingstatespacesforunfalsifiableinstances,weranJPFwithanumberofadditional configurationsofExample3.
5.4.1. Correctconfigurations
Table2 showsconfigurations inwhichJPF detected noerrors.We testedan initial configurationwitha various num-ber of producerand consumer threads andvarious buffer sizes, withone initial element. While the totalstate spacein configurations ofup tosixthreads intotalis easily tractable(JPF takesbetweena fewsecondsandtwo minutes), larger configurationsareproblematic.Configurationswithseventhreadstookbetween15and20minutestocomplete,whileeight threadscouldnotcompletewithinonehour.
Table 2
RuntimesofJPFtoanalyzethefullstatespaceinvariousscenarios.Thebufferwasfilledwithoneelementinitsinitialstate.Weshowscenarioswith verysimilaroutcomesbyshowingtherangeofparametersandmeasuredresults(asminimum. . . maximumvalues).
Threads Buffer Analysis result
prod. cons. capacity # states # instructions time [mm:ss]
1 1 1. . . 10 186. . . 230 8,112. . . 8,887 0:00. . . 0:00 1 2 1. . . 10 1,466. . . 1,608 43,597. . . 46,186 0:01. . . 0:01 2 1 2. . . 10 1,744. . . 1,844 52,638. . . 54,697 0:01. . . 0:01 2 2 1. . . 10 10,981. . . 12,449 339,300. . . 387,612 0:03. . . 0:04 2 3 1. . . 10 82,806. . . 83,396 2,714,476. . . 2,814,139 0:17. . . 0:19 3 1 3. . . 10 12,825. . . 13,045 418,015. . . 423,135 0:04. . . 0:04 3 2 2. . . 10 86,701. . . 90,241 3,082,752. . . 3,125,704 0:18. . . 0:19 3 3 1. . . 10 585,200. . . 646,643 22,420,306. . . 23,968,679 2:11. . . 2:23 3 4 1. . . 10 3,963,321. . . 4,735,873 161,745,713. . . 183,001,505 18:11. . . 21:01 4 1 4. . . 10 85,277. . . 85,753 3,119,794. . . 3,132,367 0:18. . . 0:18 4 2 3. . . 10 563,183. . . 589,886 22,484,301. . . 23,038,076 2:08. . . 2:14 4 3 2. . . 10 3,820,353. . . 4,644,356 163,788,830. . . 189,499,018 16:22. . . 19:53
4 4 1 timeout after one hour
5 1 5. . . 10 536,276. . . 537,296 21,800,221. . . 21,830,503 2:02. . . 2:02 5 2 4. . . 10 3,491,907. . . 3,600,149 153,771,259. . . 156,364,195 15:14. . . 15:24
5 3 3 timeout after one hour
Table 3
RuntimesofJPFtodetectfaultsinvariousscenarios.Thebufferwasfilledwithoneelementinitsinitialstate.Weshowscenarioswithverysimilarresults byshowingtherangeofparametersandmeasuredresults(asminimum. . . maximumvalues).
Threads Buffer Analysis result
prod. cons. capacity trace length # states # instructions time [mm:ss] 1 3. . . 10 1. . . 10 25. . . 74 26. . . 75 4,078. . . 5,520 0:00 2 1 1 10 11 3,831 0:00 3 1 1. . . 2 14 15 4,020. . . 4,041 0:00 3 2 1 37 38 4,282 0:00 4 1 1. . . 3 18. . . 20 19. . . 21 4,211. . . 4,252 0:00 4 2 1. . . 2 43. . . 47 44. . . 48 4,473. . . 4,498 0:00 4 3 1 63 64 4,748 0:00 5 1 1. . . 4 22. . . 26 23. . . 27 4,402. . . 4,461 0:00 5 2 1. . . 3 49. . . 57 50. . . 58 4,664. . . 4,714 0:00 5 3 1. . . 2 69. . . 73 70. . . 74 4,939. . . 4,964 0:00 5 4 1 91 92 5,232 0:00 . . . 10 9 1 261 262 7,922 0:00 5.4.2. Faultyconfigurations
Table3showsconfigurationsinwhichJPFdetectedadeadlock,whereaproducerorconsumerthreadcouldnotproceed because the buffer was either full or empty, respectively, andno active threads that could change that condition were available. Wetestedan initial configurationwithavariousnumberofproducer andconsumer threads andvariousbuffer sizes,withoneinitialelement.
Alargernumberofthreadsincreasesthestatespaceonlyslightly;thisismostly visiblebyalongererrortraceincases wheremorethreads areinvolved.Still,thenumberofstatesisalways small,andJPFfinds theerrorrightaway,asshown bytheverysmallnumberofinstructionsexecuted,andaruntimethatwasalwaysbelowonesecond(seeTable3).
Therefore,itcanbeseenthatJPFisveryeffectiveatfindingdefects,andcompetitivewithSyncTaskintermsofrun-time incaseswheredefectsarepresent.Forcasesthatarecorrect,JPFscalestoacoupleofthreads,butitfailsifthenumberof threads growslarger.GiventhatnoannotationsarerequiredforJPF,itisthereforeagoodchoicetotryan exampleinJPF first,beforeannotatingittotrytoprovelivenessinlargercases.
6. Relatedwork
Wepresentrelatedmethodsandtoolsthatarebasedonthefollowingapproaches:
1. softwaremodelchecking,asystematicanalysisofallpossibleoutcomesbyexecutingthesoftwareunderallschedules; 2. deductivereasoning,usingcompositionaltechniquestoreasonaboutthebehaviour ofconcurrentprograms;
3. abstractinterpretation,andinparticularthread-modulartreatments;
4. schedule synthesis andpermutation, where a safescheduleis to be found,or subsetsofall thread interleavings are investigated;
5. andaconversionoftheprogramstructuretoPetriNets. 6.1. Approachesbasedonsoftwaremodelchecking
JavaPathfinder [19] iscloselyrelatedtoourworkinthatitchecksallpossibleoutcomesofdifferentthreadinterleavings ofaconcurrentJavaprogram.Bydefault,itcheckswhetheranyassertionfailureoruncaughtexceptionoccurs,andwhether aprogramexhibitsadeadlockstate,whichisastate whereatleastoneactivethreadexiststhatcannot continuebecause itisblockedonaresource.Athreadmayblockonaresourcebecauseitmaywaitforinputfromafileornetworkchannel, trytoobtainalock,orwaitforasignalinside
wait
.Thelattertypeofdeadlockcorrespondstotheoneanalyzedby STaVe. Java Pathfinder optimizesthe state spacesearch by matching equivalent program statesandby ignoring interleavings that do not affectthe global program state [19]. Unlike our tool, Java Pathfinder executesthe full bytecode ofthe Java application undertest, so it generally doesnot scale to programs withmany threads. However, by executing the actual bytecode,itdoesnotrequireannotationstocheckagainstlivelocksinprogramsusingconditionvariables(CVs).Adrawback of Java Pathfinder is that it cannot execute native methods. Large applications typically need elaborate model libraries to execute functionality such as network communication [20], whereas STaVe only considers annotations, which can be modelled totakeintoaccountanycomplexlibraries.Inprinciple,JavaPathfindercouldhandleasimplifiedprogram(equivalenttotheSyncTaskprogram)betterthanthefull program,becausetheabstractionwouldeliminatenativecodeandreducethecomplexityoftheprogram.Itmaybepossible toisolatesubsetsofthefullprogrambyusingtheSyncTaskannotations,butthisisleftasfuturework.
Musuvathiet al. [23] present
CHESS
,atoolthatsystematicallyteststhreadinterleavingtotrytouncoversubtle concur-rencybugs.Thetool supports theWindows 32API,which featuresCVs. Ourworkshares similarities tothisone,such as theexplorationofthespaceofthreadinterleaving.However,CHESS
isconcernedwithprogramsafety,i.e.,aprogramshall notreachan errorstate.Thepresentwork,ontheotherhand,focusonalivenessproperty,i.e.,every waitingthreadwill eventuallybenotifiedandprogress.6.2. Approachesbasedondeductivereasoning
Leinoet al. [4] proposeacompositional techniqueto verifythe absenceofdeadlocksinconcurrentsystemswithboth locksandchannels.Theyusedeductivereasoningtodefine whichlocksathreadmayacquire, orto imposeanobligation fora threadto senda message.Theauthors acknowledgethat their quantitativeapproach tochannelsdoesnot apply to CVs,asmessagespassedthroughachannelarereceivedsynchronously,whileanotificationonaconditionvariableiseither received,orelseislost.
PopeeaandRybalchenko [5] presentacompositionaltechniquetoproveterminationofmulti-threadedprograms,which combinespredicateabstraction andrefinementwithrely-guarantee reasoning.The techniqueis onlydefinedforprograms that synchronizewith locks, and it cannot be easily generalized to support CVs. The reason for thisis that the thread terminationcriterionistheabsenceofinfinitecomputations;however,afinitecomputationwhereawaitingthreadisnever notifiedisincorrectlycharacterizedasterminating.
6.3. Approachesbasedonabstractinterpretation
A powerfulframework for the staticanalysisof programs is abstractinterpretation, whichallows programs to be (ab-stractly)executedinspecializedabstractdomainstoobtainalgorithmicallysoundfactsabouttheirbehaviour.Theframework isflexibleinthatitallowsprecisionoftheanalysestobetradedforperformance,andviceversa.
Todealwiththecombinatorialexplosion ofmulti-threadedprograms, someworksdevelop thread-modular analysesto achieve scalability. Miné [24] forinstance, considers locks (mutexes) as explicitsynchronization primitives, andincludes a yieldstatement. Thelocks arenot reentrant:acquiringanalreadyacquiredlockhasno effect,andsimilarly releasinga lockthatisnotacquiredbyathread.No proceduresareconsidered(butinliningcanbeusedfornon-recursiveprocedural programs),andnodynamicthreadcreation.Theaimoftheproposedmethodistodiscoverdataraces.
Inrecentfollow-upwork, MonatandMiné [25] extend theanalysistorelationaldomains, ina flow-sensitivemanner, to achieve a higher precision. The focus of the work is on numeric properties of small, but intricate mutual exclusion algorithms. The experimental results show that the method scales well, andallows the analysis of several hundreds of (small)threads.
Otherworksalsouseathread-modularanalysistodetectpotentiallyunsafeaccesses.High-leveldataracesdenoteunsafe accesspatternstotuplesofvalues [26].Localatomicityviolationsdenoteunsafeusesofshareddata [27,28].Both typesof atomicity violationshaverecentlybeenunified [29].Atomicityviolationsshow that thevalue ofaCVmaynot alwaysbe correctw. r. t.theglobalstateoftheprogram.
Anotheranalysisthatisclosetooursisadataracedetectiontoolbasedonkeyconcurrencyoperationsextractedfrom thegivenprogram [30].Similarlytoourtool,thatapproachbuildsanabstractmodelthatcontainsallrelevantconcurrency operationsonshareddata.Like STaVe’sanalysis,theirsisnotcompletelythread-modular.
Asalreadymentioned,onestrongpointoftheabove-mentionedmethodsisthatmostofthemarethread-modular.The mutual dependenciesare handled by data-flowanalysis orrely-guaranteestyle reasoning,which means that an iterative fixed-point computation is performed that invokes the thread-modular analyses on the threads in rounds, until global stabilization.
However,dataraceandatomicityanalysesdonotcoverthesignalling betweenthreads,andthereforedonotcompletely cover the semantics ofCVs. Sincewait-notify synchronizationis inherently non-local,it doesnot lend itself naturally to completely thread-modular analyses. Furthermore, it is not obvious how the analysis has to be set up to compute the interferences(asthelocaleffectsarecalled)inthecaseofCVs,andhowprecisethiscanbemade.
6.4. Schedulesynthesisandpermutation
Raychevet al. [7] presentanalgorithmthattakesasinputanon-deterministicparallelprogram,andsynthesizesa syn-chronization specificationusingCVs(andother synchronizationprimitives) sothat theprogrambecomesdeterministic, in thesensethatitproducesthesameoutputforthesameinput,regardlessofthescheduling.Thisworkdifferssubstantially fromourssincewedonotfocusondeterministicprograms(intheabovesense),andweextractasynchronization specifica-tionratherthancreateone.However,thetwoworkssharesimilarities.Forinstance,bothfocusonprogramswithconstant numberofthreads duetothe complexityofreasoningabouttheasynchronoussignalling ofCVs. Also,theyabstractaway fromothersourcesofnon-determinismthanthreadinterleaving.
Wang andHoang [31] propose a technique that permutes actions of execution traces to verify the absence of syn-chronization bugs.Theirprogrammodelconsiderslocksandconditionvariables.However,they cannotverifytheproperty consideredhere,sincetheirmethoddoesnotpermutematchingpairsofwait-notify.Forinstance,itwillnotreorderatrace where,first,athreadwaits,andthen,anotherthreadnotifies.Thus,theirmethodcannotdetectthecasewherethenotifying threadisscheduledfirst,andthewaitingthreadsuspendstheexecutionindefinitely.
6.5. ConversiontoPetri Nets
Kaiser and Pradat-Peyre [32] propose the modelling of Java monitors in Ada, and the extraction of CPNs from Ada programs. However, they donot precisely describehow theCPNs are verified,nor providea correctnessargumentabout their technique. Also, they onlyvalidate their toolon toy exampleswithfew threads.Ourtool isvalidatedon largertest cases,andonarealprogram.
Kavi et al. [33] presentPN componentsfor the synchronizationprimitives inthe Pthread libraryfor C/C++,including conditionvariables.However,theirmodellingofCVsjustallowsthesynchronizationbetweentwothreads,andnoargument ispresentedonhowtouseitwithmorethreads.
Westergaard [9] presentsatechniquetoextractCPNsforprogramsinatoyconcurrentlanguage,withlocksastheonly synchronization primitive.Our work borrows much from thiswork w. r. t. the CPN modellingand analysis. However, we analyzefull-fledgedprogramminglanguages,andaddressthecomplicationsofanalyzingprogramswithconditionvariables. Finally,VanderAalstet al. [34] presentstrategiesformodellingcomplexparallelapplicationsas CPNs.Weborrowmany ideas fromthiswork,especially themodellingofhierarchicalCPNs.However, their formalismis over-complicatedforour needs,andwethereforesimplifyittoproducemoremanageableCPNs.
7. Conclusion
We presenta technique to prove the correct synchronizationof Java programs using condition variables.Correctness here meansthat if all threads reachtheir synchronizationblocks,then all will eventually terminate thesynchronization. Ourtechniquedoesnotavoidtheexponentialblow-upofthestate spacecausedbytheinterleavingofthreads;instead,it alleviatestheproblembyisolatingthesynchronizationbehaviour.
We introduceSyncTask,asimplelanguagetocapturetherelevantaspectsofsynchronizationusingcondition variables. Also, wedefineanannotationschemeforprogrammerstomaptheexpectedsynchronizationinaJavaprogramtoa Sync-Task program.Weestablishthat thesynchronizationiscorrectw. r. t.theabove-mentionedproperty iff thecorresponding SyncTask terminates. As a proof-of-concept, to check termination we define a translation from SyncTask programs into Coloured PetriNetssuchthattheprogram terminatesiff thenetinvariablyreachesaspecialconfiguration.Theextraction ofSyncTaskfromannotatedJavaprograms,andthetranslationtoCPNs,isimplementedasthe STaVe tool.Wevalidateour technique onsome test-cases using CPNTools. Experimentsshow that our approach scales well to programs withmany threads,attheexpenseofrequiringdetailedannotationsoftheoriginalJavaprogram.