[Vm-dev] Re: primitive retry across suspension for OwnedLock waitAcquire (was: Interpreter versus StackInterpreter hierarchy)

Ben Coman btc at openinworld.com
Fri Jun 3 07:08:04 UTC 2016


On Fri, Jun 3, 2016 at 12:52 PM, Ben Coman <btc at openinworld.com> wrote:
> On Sun, May 22, 2016 at 2:15 AM, Eliot Miranda <eliot.miranda at gmail.com> wrote:
>>
>> On Fri, May 20, 2016 at 7:52 PM, Ben Coman <btc at openinworld.com> wrote:
>>>
>>> On Sat, May 21, 2016 at 4:36 AM, Clément Bera <bera.clement at gmail.com> wrote:
>>> >
>>> > On Fri, May 20, 2016 at 7:51 PM, Ben Coman <btc at openinworld.com> wrote:
>>> >>
>>> >> On Fri, May 20, 2016 at 9:25 PM, Clément Bera <bera.clement at gmail.com> wrote:
>>> >> > On Thu, May 19, 2016 at 3:42 PM, Ben Coman <btc at openinworld.com> wrote:
>>>
>> There is a better way of solving this, and that is to use a pragma to identify a method that contains such a suspension point, and have the process terminate code look for the pragma and act accordingly.  For example, the pragma could have a terminate action, sent to the receiver with the context as argument, e.g.
>>
>> Mutex>>critical: mutuallyExcludedBlock
>>     <onTerminate: #ensureMutexUnlockedInCritical:>
>>     ^lock waitAcquire
>>         ifNil: mutuallyExcludedBlock
>>         ifNotNil:[ mutuallyExcludedBlock ensure: [lock release] ]
>>
>> (and here I'm guessing...)
>>
>> Mutex>> ensureMutexUnlockedInCritical: aContext
>>     "long-winded comment explaining the corner case, referencing tests, etc, etc and how it is solved on terminate buy this method"
>>     (aContext pc = aContext initialPC
>>      and: [self inTheCorner]) ifTrue:
>>         [self doTheRightThingTM]
>>
>> So on terminate the stack is walked (it is anyway) looking for unwinds or onTerminate: markers.  Any onTerminate: markers are evaluated, and the corner case is solved.  The pragma approach also allows for visibility in the code.
>
> I think this general < onTerminate: > pragma might be useful, but I'd
> like to keep it in the back pocket for the moment while I explore
> another idea for primitive retry after a process resumes.
>
>
> I still have a concern that  #primitiveOwnedLockWaitAcquire sleeps at
> the bottom of the primitive, thus if the sleeping process is resumed,
> it continues into the critical section without having gained the
> mutex, which seems a bit fragile.  Retrying
> #primitiveOwnedLockWaitAcquire immediately after waking would
> effectively have the process sleep at the top of the primitive, and
> *not*proceed until it *really* holds the lock..
>
> So I'm thinking out loud here to formulate my thoughts, and in case
> there is some major impediment you can help me fail fast...
>
> One possibility is putting "self maybeRetryFailureAfterWaking"
> in #slowPrimitiveResponse, similar to maybeRetryFailureDueToForwarding
> and (guessing) maybeRetryFailureDueToLowMemory.  The difficulty seems
> to be that process's   primFailCode   doesn't hold across process
> suspension(??).
>
> As an aside, it seems fragile that IIUC it is possible for
> primFailCode   to be set by one process, which if then suspended will
> carry over to fail the new active process.   Perhaps somewhere like
> externalSetStackPageAndPointersForSuspendedContextOfProcess: should
> zero primFailCode.
>
> Anyway... I thought one way to retain   primFailCode   across process
> suspension might be to push it to the stack in  #transferTo:  and pop
> primFailCode from the stack in
> externalSetStackPageAndPointersForSuspendedContextOfProcess:
> except that I see that method called from a few places, so messing
> with the stack here is probably a bad idea.
>
> Another way might be for Process to get an additional instance
> variable 'suspendedPrimitiveFailCode' which again could be set in
> #transferTo:  (or even more specifically only in
> #primitiveOwnedLockWaitAcquire).  That is,
>
> #transferTo:  might have...
>
>   oldProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched.
>   objectMemory
>       storePointer: SuspendedPrimitiveFailCodeIndex
>       ofObject: oldProc
>       withValue: primFailCode
>   ...
>     primFailCode := objectMemory
>         fetchPointer: SuspendedPrimitiveFailCodeIndex
>         ofObject: newProc.
>    self externalSetStackPageAndPointersForSuspendedContextOfProcess: newProc.
>
>

Whoops, for a start, that needed to be...
    objectMemory
        storeInteger: SuspendedPrimitiveFailCodeIndex
        ofObject: oldProc
        withValue: primFailCode.
    primFailCode := objectMemory
       fetchInteger: SuspendedPrimitiveFailCodeIndex
       ofObject: newProc.


> The additional advantage here might be that the saving and restoring
> of primFailCode is localised to one method. I'm hoping that change
> (plus similar in Cog) might be sufficient to facilitate behaviour like
> this...
>
> process 1
> 1.    invokes slowPrimitiveResponse
> 2.     dispatches to primitiveOwnedLockWaitAcquire
> 3.         calls primitiveFailFor: PrimErrRetryAfterWaking
> 4.         primitiveFail saved into Process object
> 5.         process goes to sleep
>
> 6. later after process 1 woken
> 7.   primitiveFail restored from Process object
> 8.   returns to slowPrimitiveResponse
> 9.   if PrimErrRetryAfterWaking
> 10.     dispatch to primitiveOwnedLockWaitAcquire (goto step 2)
>
> Maybe there would need to be a step 9a to checkForInterrupts to avoid
> too tight a loop locking the image, but maybe this is already done
> somewhere in the suspend/resume process.
>
> So I'm now going to try coding the second way in the StackVM, and then
> look at how it might be done in Cog.  All feedback appreciated.
>
> cheers -ben


More information about the Vm-dev mailing list