[Vm-dev] Case 5959 - Broken process resume interaction with waiting semaphore

Ben Coman btc at openInWorld.com
Tue Nov 18 17:59:25 UTC 2014


I went looking at the oldest cases, and found issue 5959 interesting and 
perhaps within my scope for my first foray into the VM.

On build 40360 with the following test script...
     | sema proc |
         Transcript clear.
     sema := Semaphore new.
     proc := [ sema wait. Transcript crShow: 'signalled' ]
     proc priority: Processor highestPriority .
     proc resume.
     proc suspend.
         Transcript crShow: 1.
     proc resume.
         Transcript crShow: 2.
     sema signal.
         Transcript crShow: 'excessSignals=' ; show: sema excessSignals

Prior loading attached changeset gives...
   1
   signalled
   2
   excessSignals=1

After loading attached changeset gives...
   1
   2
   signalled
   excessSignals=0


btw, for the test script you need to add
    Semaphore>>excessSignals
        ^excessSignals


The changeset loads on top of VMMaker updating:
* CoInterpreterPrimitives>>primitiveSuspend
* CoInterpreterPrimitives>>primitiveResume
Now I probably have abused myList to keep the semaphore for 
primitiveResume to reactive, but I did poke around references to 
MyListIndex and it seemed myList was written to more than read from.


-----------
In the issue there was mention that some libraries may rely on the 
broken semantics.  Anyone know which those might be?

cheers -ben

https://pharo.fogbugz.com/default.asp?5959
-------------- next part --------------
'From Pharo3.0 of 18 March 2013 [Latest update: #30860] on 19 November 2014 at 12:59:04.381324 am'!

!CoInterpreterPrimitives methodsFor: 'process primitives' stamp: 'BenComan 11/19/2014 00:34'!
primitiveResume
	"Put this process on the scheduler's lists thus allowing it to proceed next time there is
	 a chance for processes of it's priority level.  It must go to the back of its run queue so
	 as not to preempt any already running processes at this level.  If the process's priority
	 is higher than the current process, preempt the current process."
	| proc inInterpreter myList |
	proc := self stackTop.  "rcvr"
	(objectMemory isContext: (objectMemory fetchPointer: SuspendedContextIndex ofObject: proc)) ifFalse:
		[^self primitiveFail].
	
	"Check if process we are resuming is waiting on a semaphore"
	myList := objectMemory fetchPointer: MyListIndex ofObject: proc.
	(objectMemory fetchClassOf: myList) = (objectMemory splObj: ClassSemaphore) ifTrue: [
		| excessSignals |
		excessSignals := self fetchInteger: ExcessSignalsIndex ofObject: myList.
		excessSignals > 0
			ifTrue: [
				"Consume signal and continue with resume"
				self storeInteger: ExcessSignalsIndex
				ofObject: myList
				withValue: excessSignals - 1
				]
			ifFalse: [
				"Revert #suspend action to re-wait on semaphore, and bypass resume"
				self addLastLink: proc toList: myList.
				^self.
				].
		].
		
	"We're about to switch process, either to an interpreted frame or a
	 machine code frame. To know whether to return or enter machine code
	 we have to know from whence we came.  We could have come from the
	 interpreter, either directly or via a machine code primitive.  We could have
	 come from machine code.  The instructionPointer tells us where from:"
	inInterpreter := instructionPointer >= objectMemory startOfMemory.
	(self resume: proc preemptedYieldingIf: preemptionYields from: CSResume) ifTrue:
		[self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter]

	"Personally I would like to check MyList, which should not be one of the elements of the scheduler lists.
	 But there are awful race conditions in things like should:notTakeMoreThan: that mean we can't.
	 eem 9/27/2010 23:08. e.g.

	| proc myList classLinkedList |
	proc := self stackTop.
	myList := objectMemory fetchPointer: MyListIndex ofObject: proc.
	classLinkedList := self superclassOf: (objectMemory splObj: ClassSemaphore).
	((self fetchClassOfNonInt: myList) ~= classLinkedList
	and: [objectMemory isContext: (objectMemory fetchPointer: SuspendedContextIndex ofObject: proc)]) ifFalse:
		[^self primitiveFail].
	''We're about to switch process, either to an interpreted frame or a
	 machine code frame. To know whether to return or enter machine code
	 we have to know from whence we came.  We could have come from the
	 interpreter, either directly or via a machine code primitive.  We could have
	 come from machine code.  The instructionPointer tells us where from:''
	inInterpreter := instructionPointer >= objectMemory startOfMemory.
	(self resume: proc  preemptedYieldingIf: preemptionYields from: CSResume) ifTrue:
		[self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter]"! !

!CoInterpreterPrimitives methodsFor: 'process primitives' stamp: 'BenComan 11/19/2014 00:32'!
primitiveSuspend
	"Primitive. Suspend the receiver, aProcess such that it can be executed again
	by sending #resume. If the given process is not currently running, take it off
	its corresponding list. The primitive returns the list the receiver was previously on."
	| process myList |
	process := self stackTop.
	process = self activeProcess ifTrue:
		[| inInterpreter |
		"We're going to switch process, either to an interpreted frame or a machine
		 code frame. To know whether to return or enter machine code we have to
		 know from whence we came.  We could have come from the interpreter,
		 either directly or via a machine code primitive.  We could have come from
		 machine code.  The instructionPointer tells us where from:"
		self pop: 1 thenPush: objectMemory nilObject.
		inInterpreter := instructionPointer >= objectMemory startOfMemory.
		self transferTo: self wakeHighestPriority from: CSSuspend.
		^self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter].
	myList := objectMemory fetchPointer: MyListIndex ofObject: process.
	"XXXX Fixme. We should really check whether myList is a kind of LinkedList or not
	but we can't easily so just do a quick check for nil which is the most common case."
	myList = objectMemory nilObject ifTrue:[^self primitiveFail].
	self removeProcess: process fromList: myList.
	self successful ifTrue:
		[     "Leave waiting-semaphores in myList, since we need #primitiveResume to re-wait on it"	
			(objectMemory fetchClassOf: myList) = (objectMemory splObj: ClassSemaphore) ifFalse: [
				objectMemory storePointer: MyListIndex ofObject: process withValue: objectMemory nilObject].
		 self pop: 1 thenPush: myList]! !



More information about the Vm-dev mailing list