Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3342.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3342 Author: LM Time: 10 October 2023, 1:38:40.422616 pm UUID: 6129f12c-a2f0-4b43-bfe4-d8e41bebdb55 Ancestors: VMMaker.threaded-LM.3341
Fix multiple issues regarding thread safety
1. vmOwner -1 can be used to lock the VM to a thread that is not a classical "VM Thread" (e.g. the heartbeat thread). 2. maxWaitingPriority is now an atomic.
=============== Diff against VMMaker.threaded-LM.3341 ===============
Item was changed: CoInterpreterPrimitives subclass: #CoInterpreterMT instanceVariableNames: 'cogThreadManager checkThreadActivation maxWaitingPriority foreignCallbackPriority deferThreadSwitch disowningVMThread disownCount foreignCallbackProcessSlot activeProcessAffined relinquishing processHasThreadAffinity willNotThreadWarnCount' + classVariableNames: 'DisownVMForProcessorRelinquish OwnVMForeignThreadFlag PerThreadRumpCStackSize PrimNumberRelinquishProcessor ProcessUnaffinedOnDisown ReturnToThreadSchedulingLoop VMAlreadyOwnedHenceDoNotDisown' - classVariableNames: 'DisownFlagsShift DisownVMForProcessorRelinquish OwnVMForeignThreadFlag PerThreadRumpCStackSize PrimNumberRelinquishProcessor ProcessUnaffinedOnDisown ReturnToThreadSchedulingLoop VMAlreadyOwnedHenceDoNotDisown' poolDictionaries: 'VMThreadingConstants' category: 'VMMaker-Multithreading'!
Item was changed: ----- Method: CoInterpreterMT class>>initializeSchedulerIndices (in category 'initialization') ----- initializeSchedulerIndices super initializeSchedulerIndices. "Class Process" "The thread id of a process is either nil or a SmallInteger that defines how a process binds to threads. If nil, the process may run on any thread. The least significant bit of threadId is a flag. The most significant bits are a threadId. If the threadId is nil the process can run on any thread. If the flag (least significant bit) is set then If the threadId is positive the process can only be run on the thread with that thread Id. If the threadId is negative the process must not be run on the thread with that thread Id. If the flag (least significant bit) is not set then the thread id will not be negative and if non-zero is the id of the thread the process is currenty running on The flag is probably a mistake..."
"In part, what's really going on here is an attempt to deal with threading on Mac. Events can only be delivered on the GUi thread. To avoid blocking and/or crashing the VM by making a blocking (e.g. FFI) call on the GUI thread we want a nice way of preventing a process from running on the GUI thread. We can then use a process whose threadId precludes running on the GUI thread to make blocking calls. The alternative, of arranging that events are delivered to a thread the VM does not run on, is problematic; it cannot support event callbacks."
"So if we simplify, and threadId is only instructive to the VM, (i.e. can say ''run on a given thread'', or ''don't run on a given thread'', and ''don't care; run on any thread'') and does not necessarily hold the threadId of the thread the process is currenty bound to, how do we locate the threadId for a process temporarily affined to a thread for the duration of an FFI call? Note that a process *must* be bound to a thread for the duration of a threaded call (or foreign callback) so that return occurs on the correct thread. We can use the least significant bit to mean ''temporarily affined'', but we need to support ''don't run on this thread''. We could use bit fields (yuck); we could allocate a two field object and assign it to threadId when setting a process to not run on a given thread.
This isn't so bad; use negative values to mean ''don't run on this thread'', and positive values to mean ''run on this thread''. Split the smallest SmallInteger (32-bit, 1 bit sign, 2-bit tags, leaving 29//2) into two 14 bit fields. The least significant 14 bits are the thread id the receiver is temporarily affined to. The most significant 14 bits are the thread id of the thread the proess is either bound to or excluded from. If zero, the process is agnostic. See CogThreadManager>>#maxNumThreads" ThreadIdIndex := 4. ThreadIdShift := 14. "could be 30 in 64-bits"
"disown result/own argument flags" OwnVMForeignThreadFlag := 1. VMAlreadyOwnedHenceDoNotDisown := 2. ProcessUnaffinedOnDisown := 4. "& defined in StackInterpreter are..." DisownVMForFFICall := 16. DisownVMForThreading := 32. "N.B. some of these DisownFlags are replicated in platforms/Cross/vm/sqVirtualMachine.h. Hence they should always be initialized." + DisownVMForProcessorRelinquish := 64.! - DisownVMForProcessorRelinquish := 64. - - DisownFlagsShift := DisownVMForProcessorRelinquish highBit!
Item was changed: ----- Method: CoInterpreterMT>>cedeToHigherPriorityThreads (in category 'process primitive support') ----- cedeToHigherPriorityThreads "Invoked from checkForEventsMayContextSwitch: to switch threads if a thread wanting to acquire the VM has higher priority than the active process." + | activeProc processAffinity activeContext activePriority activeThread vmThread waitingPriority | - | activeProc processAffinity activeContext activePriority activeThread vmThread | <var: #activeThread type: #'CogVMThread *'> <var: #vmThread type: #'CogVMThread *'> <inline: false> activeProc := self activeProcess. activePriority := self quickFetchInteger: PriorityIndex ofObject: activeProc. processAffinity := self ownerIndexOfProcess: activeProc. activeThread := cogThreadManager currentVMThread. self assert: (cogThreadManager threadIndex: activeThread index isCompatibleWith: processAffinity).
+ waitingPriority := self getMaxWaitingPriority. activeThread priority: activePriority. vmThread := cogThreadManager highestPriorityThreadIfHigherThan: activePriority + expectedMax: waitingPriority. + - expectedMax: maxWaitingPriority. (vmThread isNil "no waiting thread of sufficiently high priority. Do not switch." or: [vmThread = activeThread]) "The activeProcess needs to run on a different thread. Leave this to threadSwitchIfNecessary:from: in checkForEventsMayContextSwitch:" ifTrue: + [waitingPriority > activePriority ifTrue: + ["We found no thread of sufficiently high priority, even though waitingPriority indicated there should be one. + So reduce the waiting priority back to the priority of the currently active process." + self reduceWaitingPriorityFrom: waitingPriority to: activePriority]. - [maxWaitingPriority > activePriority ifTrue: - [maxWaitingPriority := activePriority]. ^self].
self assert: vmThread priority > activePriority. self assert: vmThread ~= cogThreadManager currentVMThread. self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer.
+ waitingPriority > vmThread priority ifTrue: + [self reduceWaitingPriorityFrom: waitingPriority to: vmThread priority]. - maxWaitingPriority > vmThread priority ifTrue: - [maxWaitingPriority := vmThread priority]. statProcessSwitch := statProcessSwitch + 1. activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. objectMemory storePointer: SuspendedContextIndex ofObject: activeProc withValue: activeContext. self ensurePushedInstructionPointer. self externalWriteBackHeadFramePointers. self putToSleep: activeProc yieldingIf: preemptionYields. "Transcript cr; print: #cedeToHighestPriorityThreadIfHigherThan:; cr. self printExternalHeadFrame. self print: 'ip: '; printHex: self instructionPointer. Transcript cr; flush." self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSCheckEvents!
Item was changed: ----- Method: CoInterpreterMT>>disownVM: (in category 'vm scheduling') ----- disownVM: flags "Release the VM to other threads and answer the current thread's index. Currently valid flags: DisownVMForFFICall - informs the VM that it is entering an FFI call DisownVMForThreading - informs the VM that it is entering code during which threading should be permitted OwnVMForeignThreadFlag - indicates lowest-level entry from a foreign thread - not to be used explicitly by clients - only set by ownVMFromUnidentifiedThread VMAlreadyOwnedHenceDoNotDisown - indicates an ownVM from a callback was made when the vm was still owned. - not to be used explicitly by clients - only set by ownVMFromUnidentifiedThread
This is the entry-point for plugins and primitives that wish to release the VM while performing some operation that may potentially block, and for callbacks returning back to some blocking operation. If this thread does not reclaim the VM before- hand then when the next heartbeat occurs the thread manager will schedule a thread to acquire the VM which may start running the VM in place of this thread.
N.B. Most of the state needed to resume after preemption is set in preemptDisowningThread." <public> <inline: false> <returnTypeC: #'void *'> + | vmThread activeProc | + self assert: flags >= 0. - | vmThread | - self assert: (flags >= 0 and: [flags < (1 bitShift: DisownFlagsShift)]). self assert: self successful. cogit recordEventTrace ifTrue: [self recordTrace: TraceDisownVM thing: (objectMemory integerObjectOf: flags) source: 0]. processHasThreadAffinity ifFalse: [willNotThreadWarnCount < 10 ifTrue: [self print: 'warning: VM parameter 48 indicates Process doesn''t have threadId; VM will not thread'; cr. willNotThreadWarnCount := willNotThreadWarnCount + 1]]. vmThread := cogThreadManager currentVMThread. (flags anyMask: VMAlreadyOwnedHenceDoNotDisown) ifTrue: [disowningVMThread := vmThread. vmThread setVmThreadState: CTMUnavailable. ^nil]. self assertCStackPointersBelongToCurrentThread. self assertValidNewMethodPropertyFlags. self cCode: '' inSmalltalk: [cogThreadManager saveRegisterStateForCurrentProcess. cogThreadManager clearRegisterStates.]. (flags anyMask: DisownVMForProcessorRelinquish) ifTrue: [| proc | (proc := objectMemory splObj: foreignCallbackProcessSlot) ~= objectMemory nilObject ifTrue: [foreignCallbackPriority := self quickFetchInteger: PriorityIndex ofObject: proc]. relinquishing := true. self sqLowLevelMFence]. disownCount := disownCount + 1. "If we're disowning the VM because there's no active process to run, there's nothing to preempt later, so don't indicate that there's a disowningVMThread that needs to be restored later." + activeProc := self activeProcess. + activeProc ~= objectMemory nilObject + ifTrue: [disowningVMThread := vmThread. + vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: activeProc).]. - self activeProcess ~= objectMemory nilObject - ifTrue: [disowningVMThread := vmThread].
"OwnVMForeignThreadFlag indicates lowest-level of entry by a foreign thread. If that's where we are then release the vmThread. Otherwise indicate the vmThread is off doing something outside of the VM." (flags anyMask: OwnVMForeignThreadFlag) ifTrue: ["I don't think this is quite right. Josh's use case is creating some foreign thread and then registering it with the VM. That's not the same as binding a process to a foreign thread given that the foreign callback process is about to terminate anyway (it is returning from a callback here). So do we need an additional concept, that of a vmThread being either of the set known to the VM or floating?" self flag: 'issue with registering foreign threads with the VM'. (self isBoundProcess: self activeProcess) ifFalse: [cogThreadManager unregisterVMThread: vmThread]] ifFalse: [vmThread setVmThreadState: CTMUnavailable].
vmThread disownFlags: (flags bitOr: (activeProcessAffined ifTrue: [0] ifFalse: [ProcessUnaffinedOnDisown])). cogThreadManager releaseVM. ^vmThread!
Item was added: + ----- Method: CoInterpreterMT>>getMaxWaitingPriority (in category 'accessing') ----- + getMaxWaitingPriority + + ^ self atomic_load: (self addressOf: maxWaitingPriority)!
Item was changed: ----- Method: CoInterpreterMT>>initialize (in category 'initialization') ----- initialize super initialize. relinquishing := checkThreadActivation := deferThreadSwitch := false. + foreignCallbackPriority := disownCount := willNotThreadWarnCount := 0. + maxWaitingPriority := AtomicValue new.! - foreignCallbackPriority := maxWaitingPriority := disownCount := willNotThreadWarnCount := 0!
Item was changed: ----- Method: CoInterpreterMT>>initializeInterpreter: (in category 'initialization') ----- initializeInterpreter: bytesToShift super initializeInterpreter: bytesToShift. foreignCallbackProcessSlot := (objectMemory lengthOf: objectMemory specialObjectsOop) > ForeignCallbackProcess ifTrue: [ForeignCallbackProcess] ifFalse: [NilObject]. + + self atomic_store: (self addressOf: maxWaitingPriority) _: 0.! - !
Item was changed: ----- Method: CoInterpreterMT>>ownVMFromUnidentifiedThread (in category 'vm scheduling') ----- ownVMFromUnidentifiedThread "Attempt to take ownership from a thread that as yet doesn't know its index. This supports callbacks where the callback could originate from any thread. Answer 0 if the owning thread is known to the VM. Answer 1 if the owning thread is unknown to the VM and now owns the VM. Answer -1 if the owning thread is unknown to the VM and fails to own the VM. Answer -2 if the owning thread is unknown to the VM and there is no foreign callback process installed." | count threadIndex vmThread | <var: #vmThread type: #'CogVMThread *'> <inline: false> self cCode: [] inSmalltalk: [self halt: 'TODO: Implement processor register switching']. (threadIndex := cogThreadManager ioGetThreadLocalThreadIndex) ~= 0 ifTrue: [ "this is a callback from a known thread" (cogThreadManager vmOwnerIs: threadIndex) ifTrue: "the VM has not been disowned" [self assert: (disowningVMThread isNil or: [disowningVMThread = self currentVMThread]). disowningVMThread := nil. self currentVMThread setVmThreadState: CTMAssignableOrInVM. + ^VMAlreadyOwnedHenceDoNotDisown]]. + + (threadIndex = 0 and: [foreignCallbackPriority = 0]) ifTrue: - ^VMAlreadyOwnedHenceDoNotDisown]. - ^self cCode: [nil] inSmalltalk: [self error: 'TODO: Needs to take a vmThread, not the index'."self ownVM: threadIndex"]]. - foreignCallbackPriority = 0 ifTrue: [^-2]. count := 0. + + "Before we can proceed, we need to temporarily lock the vm, so we can either find our CogVMThread struct or allocate a new one." + cogThreadManager acquireVMForIndex: threadIndex withPriority: foreignCallbackPriority. + + threadIndex ~= 0 + ifTrue: ["this is a callback from a known thread. Simply own the VM for that thread." + vmThread := cogThreadManager vmThreadAt: threadIndex. + ^ self ownVM: vmThread]. + "If the current thread doesn't have an index it's new to the vm and we need to allocate a new threadInfo, failing if we can't. We also need a process in the foreignCallbackProcessSlot upon + which to run the thread's eventual callback." + [(objectMemory splObj: foreignCallbackProcessSlot) = objectMemory nilObject] whileTrue: - which to run the thread's eventual callback." - [[cogThreadManager tryLockVMOwnerTo: cogThreadManager ioCurrentOSThread asUnsignedInteger] whileFalse: - [self waitingPriorityIsAtLeast: foreignCallbackPriority. - cogThreadManager ioTransferTimeslice]. - (objectMemory splObj: foreignCallbackProcessSlot) ~= objectMemory nilObject] whileFalse: [cogThreadManager releaseVM. (count := count + 1) > 1000 ifTrue: [^-2]. + cogThreadManager ioMilliSleep: 1. + cogThreadManager acquireVMForIndex: threadIndex withPriority: foreignCallbackPriority]. - cogThreadManager ioMilliSleep: 1].
vmThread := cogThreadManager unusedThreadInfo. "N.B. Keep the VM locked anonymously so that we reserve the non-nil ForeignCallbackProcess for this thread, avoiding the race between competing foreign callbacks. The acquireVMFor: in ownVM: will set the vmOwner to the actual index. So only unlock on failure." vmThread ifNil: [cogThreadManager releaseVM. ^-1]. cogThreadManager setVMOwner: vmThread index. vmThread + priority: foreignCallbackPriority; + disownFlags: OwnVMForeignThreadFlag; + setVmThreadState: CTMWantingOwnership. - setVmThreadState: CTMWantingOwnership; - priority: foreignCallbackPriority. cogThreadManager registerVMThread: vmThread. + ^self ownVM: vmThread! - ^self ownVM: vmThread index + OwnVMForeignThreadFlag!
Item was changed: ----- Method: CoInterpreterMT>>preemptDisowningThread (in category 'vm scheduling') ----- preemptDisowningThread "Set the relevant state for disowningVMThread so that it can resume after being preempted and set disowningVMThread to nil to indicate preemption.
N.B. This should only be sent from checkPreemptionOfDisowningThread.
There are essentially four things to do. a) save the VM's notion of the current C stack pointers; these are pointers into a thread's stack and must be saved and restored in thread switch. b) save the VM's notion of the current Smalltalk execution point. This is simply the suspend half of a process switch that saves the current context in the current process. c) add the process to the thread's set of AWOL processes so that the scheduler won't try to run the process while the thread has disowned the VM. d) save the in-primitive VM state, newMethod and argumentCount
ownVM: will restore the VM context as of disownVM: from the above when it finds it has been preempted."
| activeProc activeContext preemptedThread | <var: #preemptedThread type: #'CogVMThread *'> <inline: false> self assert: disowningVMThread notNil. self assert: (disowningVMThread vmThreadState = CTMUnavailable or: [disowningVMThread vmThreadState = CTMWantingOwnership]). self assertCStackPointersBelongToDisowningThread. cogit recordEventTrace ifTrue: [self recordTrace: TracePreemptDisowningThread thing: (objectMemory integerObjectOf: disowningVMThread index) source: 0]. disowningVMThread cStackPointer: CStackPointer. disowningVMThread cFramePointer: CFramePointer. activeProc := self activeProcess. self assert: (objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject. objectMemory storePointer: MyListIndex ofObject: activeProc withValue: (objectMemory splObj: ProcessInExternalCodeTag). activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. objectMemory storePointer: SuspendedContextIndex ofObject: activeProc withValue: activeContext. "The instructionPointer must be pushed because the convention for inactive stack pages is that the instructionPointer is top of stack. We need to know if this primitive is called from machine code because the invariant that the return pc of an interpreter callee calling a machine code caller is ceReturnToInterpreterPC must be maintained." self push: instructionPointer. self externalWriteBackHeadFramePointers. "Since pushing the awol process may realloc disowningVMThread we need to reassign. But since we're going to nil disowningVMThread anyway we can assign to a local." preemptedThread := cogThreadManager pushAWOLProcess: activeProc on: disowningVMThread. disowningVMThread := nil. - preemptedThread priority: (self quickFetchInteger: PriorityIndex ofObject: activeProc). (self ownerIndexOfProcess: activeProc) = 0 ifTrue: [self setOwnerIndexOfProcess: activeProc to: preemptedThread index bind: false]. preemptedThread newMethodOrNull: newMethod; argumentCount: argumentCount; inMachineCode: instructionPointer <= objectMemory startOfMemory!
Item was changed: ----- Method: CoInterpreterMT>>primitiveProcessBindToThreadId (in category 'process primitives') ----- primitiveProcessBindToThreadId "Attempt to bind the receiver to the thread with the id of the argument or nil, where the receiver is a Process. If successful the VM will ensure that there are at least id many threads active." + | aProcess id waitingPriority activePriority | - | aProcess id | <export: true> self cCode: [] inSmalltalk: [cogThreadManager isNil ifTrue: [^self primitiveFail]]. processHasThreadAffinity ifFalse: [^self primitiveFailFor: PrimErrUnsupported]. id := self stackTop. aProcess := self stackValue: 1. ((id = objectMemory nilObject or: [(objectMemory isIntegerObject: id) and: [id ~= (objectMemory integerObjectOf: 0)]]) and: [(objectMemory isPointers: aProcess) and: [(objectMemory slotSizeOf: aProcess) >= (ThreadIdIndex + 1)]]) ifFalse: [^self primitiveFailFor: PrimErrBadArgument]. id := id = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: id]. id abs >= cogThreadManager maxNumThreads ifTrue: [^self primitiveFailFor: PrimErrLimitExceeded]. (self bindProcess: aProcess toId: id) ifNotNil: [:ec| ^self primitiveFailFor: ec]. self methodReturnReceiver.
+ waitingPriority := self getMaxWaitingPriority. + activePriority := self quickFetchInteger: PriorityIndex ofObject: aProcess. id := self ownerIndexOfProcess: aProcess. (aProcess = self activeProcess and: [(activeProcessAffined := id ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: id) not]]) ifTrue: + [activePriority < waitingPriority ifTrue: + [self reduceWaitingPriorityFrom: waitingPriority to: activePriority "TODO: Check if this is correct?"]. - [(self quickFetchInteger: PriorityIndex ofObject: aProcess) < maxWaitingPriority ifTrue: - [maxWaitingPriority = self quickFetchInteger: PriorityIndex ofObject: aProcess]. self threadSwitchIfNecessary: aProcess from: CSThreadBind]!
Item was added: + ----- Method: CoInterpreterMT>>reduceWaitingPriorityFrom:to: (in category 'accessing') ----- + reduceWaitingPriorityFrom: existingWaitingPriority to: newMaxPriority + <var: #existing type: #int> + | existing | + self cCode: [existing := existingWaitingPriority] + inSmalltalk: [existing := AtomicValue new. + existing value: existingWaitingPriority.]. + "This CPXCHG may fail, that's fine though, as there may have been + another thread that increased the priority in the meantime. + In that case that threads priority is the correct one to use." + ^ self atomic: (self addressOf: maxWaitingPriority) + _compare: (self addressOf: existing) + _exchange_strong: newMaxPriority!
Item was removed: - ----- Method: CoInterpreterMT>>setMaxWaitingPriorityTo: (in category 'process primitive support') ----- - setMaxWaitingPriorityTo: minPriority - maxWaitingPriority := minPriority!
Item was changed: ----- Method: CoInterpreterMT>>threadSchedulingLoopImplementation: (in category 'vm scheduling') ----- threadSchedulingLoopImplementation: vmThread "Enter a loop attempting to run the VM with the highest priority process and blocking on the thread's OS semaphore when unable to run that process. We will return to this via threadSwitchIfNecessary:from: which is called in the middle of transferTo:from: once the active process has been stored in the scheduler." <var: #vmThread type: #'CogVMThread *'> <inline: false> self _setjmp: vmThread reenterThreadSchedulingLoop. [self assert: vmThread vmThreadState = CTMAssignableOrInVM. (cogThreadManager tryLockVMOwnerTo: vmThread index) ifTrue: ["Yay, we're the VM owner!!" "If relinquishing is true, then primitiveRelinquishProcessor has disowned the VM and only a returning call or callback should take ownership in that case." relinquishing ifFalse: [self tryToExecuteSmalltalk: vmThread]. + "tryToExecuteSmalltalk: may return if there's no runnable process. + Usually it doesn't return, but jumps straight back to the _setjmp at the top of this function, + so this is only reached in case there's no runnable process." + self disownVM: DisownVMForThreading]. - self disownVM: DisownVMForThreading.]. cogThreadManager waitForWork: vmThread. true] whileTrue!
Item was changed: ----- Method: CoInterpreterMT>>threadSwitchIfNecessary:from: (in category 'process primitive support') ----- threadSwitchIfNecessary: newProc from: sourceCode "Invoked from transferTo:from: or primitiveProcessBindToThreadId to switch threads if the new process is bound or affined to some other thread." | newProcOwnerIndex vmThread activeContext | self assert: (cogThreadManager vmOwnerIs: cogThreadManager ioGetThreadLocalThreadIndex). deferThreadSwitch ifTrue: [^self].
cogThreadManager assertValidProcessorStackPointersForIndex: cogThreadManager getVMOwner.
"If the current process is unaffined or it is affined to the current thread we're ok to run, but we should yield asap if a higher-priority thread wants the VM." newProcOwnerIndex := self ownerIndexOfProcess: newProc. ((activeProcessAffined := newProcOwnerIndex ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcOwnerIndex) not]) ifFalse: + [(self quickFetchInteger: PriorityIndex ofObject: newProc) < self getMaxWaitingPriority ifTrue: - [(self quickFetchInteger: PriorityIndex ofObject: newProc) < maxWaitingPriority ifTrue: [checkThreadActivation := true. self forceInterruptCheck]. ^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner." self cCode: '' inSmalltalk: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcOwnerIndex }].
"We at least need to externalize the stack pointers to enable a thread switch..." (objectMemory fetchPointer: SuspendedContextIndex ofObject: newProc) = objectMemory nilObject ifTrue: [self assert: newProc = self activeProcess. self push: instructionPointer. self externalWriteBackHeadFramePointers. false ifTrue: "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution." [activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext]].
newProcOwnerIndex < 0 ifTrue: [self assert: newProcOwnerIndex negated = cogThreadManager getVMOwner. vmThread := cogThreadManager ensureWillingThread. self deny: vmThread index = cogThreadManager getVMOwner. self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcOwnerIndex)] ifFalse: [vmThread := cogThreadManager vmThreadAt: newProcOwnerIndex. vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc). vmThread vmThreadState = CTMUnavailable ifTrue: [vmThread setVmThreadState: CTMWantingOwnership]]. self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSSwitchIfNeccessary!
Item was changed: + ----- Method: CoInterpreterMT>>waitingPriorityIsAtLeast: (in category 'accessing') ----- - ----- Method: CoInterpreterMT>>waitingPriorityIsAtLeast: (in category 'process primitive support') ----- waitingPriorityIsAtLeast: minPriority "Set the maxWaitingPriority to at least minPriority on behalf of a thread wanting to acquire the VM. If maxWaitingPriority is increased, schedule a thread activation check asap." + <var: #currentWaitingPriority type: #int> + | currentWaitingPriority didIncrease | + self cCode: [currentWaitingPriority := self getMaxWaitingPriority.] + inSmalltalk: [currentWaitingPriority := AtomicValue new. + currentWaitingPriority value: self getMaxWaitingPriority]. + + didIncrease := false. + [(self cCode: [currentWaitingPriority] inSmalltalk: [currentWaitingPriority value]) >= minPriority + or: [didIncrease := self atomic: (self addressOf: maxWaitingPriority) + _compare: (self addressOf: currentWaitingPriority) + _exchange_strong: minPriority]] whileFalse. + didIncrease + ifTrue: [ self assert: currentWaitingPriority < minPriority. + checkThreadActivation := true. + self forceInterruptCheck]! - maxWaitingPriority < minPriority ifTrue: - [maxWaitingPriority := minPriority. - checkThreadActivation := true. - self forceInterruptCheck]!
Item was changed: ----- Method: CogThreadManager class>>declareCVarsIn: (in category 'translation') ----- declareCVarsIn: cCodeGen cCodeGen removeVariable: 'coInterpreter'; removeVariable: 'cogit'; removeVariable: 'threadLocalStorage'; removeVariable: 'processorOwner'; removeVariable: 'registerStates'. cCodeGen var: #threads type: #'CogVMThread **'; var: #vmOSThread type: #sqOSThread; + var: #vmOwner type: #'volatile atomic_int'; + var: #maxWaitingPriority type: #'volatile atomic_int'. - var: #vmOwner type: #'volatile atomic_int'. cCodeGen addHeaderFile: '<stdatomic.h>'!
Item was changed: ----- Method: CogThreadManager>>acquireVMFor: (in category 'public api') ----- acquireVMFor: vmThread "Attempt to acquire the VM, eventually blocking until it becomes available. Spin until the maxWaitingPriority has been updated if it is lower than this thread's priority." <returnTypeC: #'CogVMThread *'> <var: #vmThread type: #'CogVMThread *'> self assert: vmThread index = self ioGetThreadLocalThreadIndex. self assert: (vmThread vmThreadState = CTMUnavailable or: [vmThread vmThreadState = CTMWantingOwnership]). (self tryLockVMOwnerTo: vmThread index) ifTrue: [vmThread setVmThreadState: CTMAssignableOrInVM] ifFalse: [vmThread setVmThreadState: CTMWantingOwnership. + [self tryLockVMOwnerTo: vmThread index] whileFalse: - [(self vmOwnerIs: vmThread index) - or: [self tryLockVMOwnerTo: vmThread index]] whileFalse: [vmThread priority ifNotNil: [coInterpreter waitingPriorityIsAtLeast: vmThread priority]. (self vmOwnerIs: vmThread index) ifFalse: [self ioWaitOnOSSemaphore: (self addressOf: vmThread osSemaphore)]]]. coInterpreter assertProcessorStackPointersBelongToCurrentThread. vmOSThread := vmThread osThread. vmThread setVmThreadState: CTMAssignableOrInVM. ^vmThread!
Item was added: + ----- Method: CogThreadManager>>acquireVMForIndex:withPriority: (in category 'public api') ----- + acquireVMForIndex: threadIndex withPriority: aPriority + + [self tryLockVMOwnerTo: (threadIndex > 0 ifTrue: [threadIndex] ifFalse: [CTMUnknownOwner])] whileFalse: + [self waitingPriorityIsAtLeast: aPriority. + self ioTransferTimeslice].!
Item was changed: + ----- Method: CogThreadManager>>doTryLockVMOwnerTo: (in category 'Cogit lock implementation') ----- - ----- Method: CogThreadManager>>doTryLockVMOwnerTo: (in category 'simulation') ----- doTryLockVMOwnerTo: threadIndex "In the simulation, this is being called by #simulateTryLockVMOwnerTo:, in C this method will just be called directly. Returns true if the vmOwner has been successfully set to the given thread index." <inline: #always> <var: #expected type: #int> | expected | expected := self cCode: 0 inSmalltalk: [AtomicValue newFrom: 0]. ^ (self atomic: (self addressOf: vmOwner) _compare: (self addressOf: expected) _exchange_strong: threadIndex) or: ["We may already be vmOwner. The current vmOwner will be stored in expected. However, if an unknown owner is present, we cannot assume that's us!!" expected = threadIndex and: [threadIndex ~= CTMUnknownOwner]]!
Item was changed: ----- Method: CogThreadManager>>highestPriorityThreadIfHigherThan:expectedMax: (in category 'public api') ----- highestPriorityThreadIfHigherThan: activePriority expectedMax: maxPriority "Answer the first vmThread waiting to acquire the VM that is of higher priority than activePriority, or answer nil if none. If there is a higher priority thread then set the coInterpreter's maxWaitingPriority to either the priority of the next highest priority vmThread, or to 0 if none is waiting." <returnTypeC: #'CogVMThread *'> | vmThread highest nextHighest | <var: #vmThread type: #'CogVMThread *'> <var: #highest type: #'CogVMThread *'> <var: #nextHighest type: #'CogVMThread *'> highest := nextHighest := nil. "To make this fair we could remember the last index at which we found the highest and start the search at the following index." 1 to: numThreads do: [:i| vmThread := threads at: i. vmThread vmThreadState = CTMWantingOwnership ifTrue: [self assert: vmThread priority <= maxPriority. highest isNil ifTrue: [highest := vmThread] ifFalse: [vmThread priority > highest priority ifTrue: [nextHighest := highest. highest := vmThread] ifFalse: [nextHighest isNil ifTrue: [nextHighest := vmThread] ifFalse: [vmThread priority > nextHighest priority ifTrue: [nextHighest := vmThread]]]]]]. highest isNil ifTrue: [^nil].
highest priority <= activePriority ifTrue: [^nil]. + coInterpreter waitingPriorityIsAtLeast: (nextHighest isNil - coInterpreter setMaxWaitingPriorityTo: (nextHighest isNil ifTrue: [0] ifFalse: [nextHighest priority]). ^highest!
Item was removed: - ----- Method: CogVMSimulator>>setMaxWaitingPriorityTo: (in category 'multi-threading simulation switch') ----- - setMaxWaitingPriorityTo: minPriority - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #setMaxWaitingPriorityTo: - withArguments: {minPriority} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
vm-dev@lists.squeakfoundation.org