[BUG] Crash while working on a Future.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

[BUG] Crash while working on a Future.

Michael van der Gulik
Hi all.

I've got this really difficult bug.

Attached is a small ChangeSet containing subset of classes that I'm
using. When I execute the following, Squeak crashes:

Future doing: [ByteStream].

The intended behaviour is that the value is returned immediately and is
usable (any invocation will block), but is filled in with the value of
the block when that block has finished executing.

It works for a few simple cases, but unfortunately the image has a spasm
and dies with the above.

Only alt-d this. Don't inspect or debug it straight off; you'll freeze
the image. Once the 'halt:' has popped up, you should be able to use
that debugger, although you might have to define instVarAt:,
instVarAt:put: and maybe one or two others in MessageCapture.

There is also debugging output logged to squeak.image.log.

I'm stuck on this. I have a feeling that it is more complicated that
what immediately is apparent.

Many thanks to any and all who have a crack at this! Oh, and the
attached code is released under the MIT license.

Mikevdg.


'From Squeak3.8 of ''5 May 2005'' [latest update: #6665] on 21 June 2006 at 11:21:39 pm'!
Object subclass: #Future
        instanceVariableNames: 'semaphore messageCapture'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!Future commentStamp: 'mvdg 6/5/2006 19:59' prior: 0!
A future is an object which can be assigned as a return value, but doesn't become that value until that value has been computed by something else in the system. So it is a "future" in terms of a "future value". It lets code fork off a computation thread but still immediately return a value.

This class is actually only a manager. The actual object which will become: the value is a MessageCapture.!

Object subclass: #Logger
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!Logger commentStamp: 'mvdg 11/12/2005 08:50' prior: 0!
I just write log messages to a file. See my class methods.

This class is a quick hack and needs to be replaced with something more substantial.!

Logger class
        instanceVariableNames: 'outputFile doNotInterupt'!
ProtoObject subclass: #MessageCapture
        instanceVariableNames: 'target receiver proxyDestroySemaphore numCurrentInvokes'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!MessageCapture commentStamp: 'mikevdg 8/18/2003 10:18' prior: 0!
I intercept and forward messages going to another object.

To install me on an object:

Proxy interceptFrom: aTargetObject forwardTo: aReceivingObject

The receiving object can be any object, but must implement an "acceptFromProxy: theProxy message: aMessage" method.

Instance variables:
        target: the object I am capturing messages to.
        receiver: the object which is going to get those messages.

Don't forget about the return value from Intercepted messages!!

TODO: many methods are passed through to the target; this is not the desired behaviour.
TODO: there shouldn't be a target. Not all algorithms need it.!

Inspector subclass: #MessageCaptureInspector
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!
Exception subclass: #RemoteException
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!RemoteException commentStamp: 'mvdg 11/12/2005 08:23' prior: 0!
Currently used only in the MessageCapture class. !


!ProtoObject methodsFor: 'should be in Squeak' stamp: 'mvdg 6/21/2006 23:19'!
becomeForward: otherObject
        "Primitive. All variables in the entire system that used to point
        to the receiver now point to the argument.
        Fails if either argument is a SmallInteger."

        (Array with: self)
                elementsForwardIdentityTo:
                        (Array with: otherObject)! !


!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:33'!
acceptFromProxy: p message: m
        " Make the calling process wait. "
        Logger show: 'Future got: ', (m selector).
        semaphore wait.
        Logger show: 'Future doing: ', (m selector), 'on a ', (self class name).
        ^ messageCapture perform: (m selector) withArguments: (m arguments).
! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/21/2006 23:16'!
doing: aBlock
        | |
        messageCapture := MessageCapture new.
        messageCapture proxyReceiver: self.

        [ | target |
                target := aBlock value. " <--- This is the main part of the Future "
                messageCapture proxyTarget: target.
                Logger show: 'Future resolving to a ', (target class name).
                (ByteString == target) ifTrue: [self halt: 'about to crash.'].
          messageCapture proxyDestroy.
                Logger show: 'Future resolved to a ', (messageCapture class name asString).
          [semaphore isSignaled] whileFalse: [ semaphore signal ].
        ] fork.

        ^ messageCapture.! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:29'!
initialize
        semaphore := Semaphore new.
! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:56'!
youAreMyReceiver: bob! !


!Future class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:27'!
doing: aBlock
        ^ self new doing: aBlock.! !


!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 11/12/2005 09:00'!
close
        outputFile close.! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:01'!
ensureOpen
        outputFile ifNil: [
                outputFile := CrLfFileStream new open: (SmalltalkImage current imageName,'.log') forWrite: true.
        ] ifNotNil: [
                outputFile ensureOpen.
                outputFile setToEnd.
        ].
        doNotInterupt ifNil: [ doNotInterupt := Semaphore forMutualExclusion ].! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 11/12/2005 09:11'!
reset
        outputFile := nil.! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:12'!
show: aMessage
        self ensureOpen.
        doNotInterupt critical: [
                outputFile nextPutAll: (Time now asString). "Probably better done using Streams..."
                outputFile nextPutAll: ' ',aMessage.
                (aMessage includes: (Character cr)) ifFalse: [
                        outputFile nextPut: Character cr. ].
        ]
! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:03'!
stackTrace
        | sender |
        self ensureOpen.
        doNotInterupt critical: [
                self show: 'Stack trace follows --------------'.
                sender := thisContext sender.
                [ sender isNil ] whileFalse: [
                        sender printOn: outputFile.
                        outputFile cr.
                        sender := sender sender.
                ].
        ]
! !


!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/21/2006 22:55'!
doesNotUnderstand: m
        " doesNotUnderstand gets called on every message without a method. Every message thus. "
        | rv |
        " If you get a doesNotUnderstand in the line below, look further down the stack.
         Also, try calling proxyReset on your replicated object. "
        "numCurrentInvokes := numCurrentInvokes + 1.
        (numCurrentInvokes > 10) ifTrue: [ RemoteException new signal: 'MessageCapture possibly in recursive loop.' ]. "

        m lookupClass: nil.
        Logger show: 'MessageCapture got message: <<', (m asString), '>>'.
        receiver ifNil: [RemoteException new signal: 'MessageCapture has no receiver.'].

        [ rv := receiver acceptFromProxy: self message: m ]
                ifError: [ :e :r |
                        "numCurrentInvokes := numCurrentInvokes - 1."
                        Logger show: 'MessageCapture got error: ', (e asString).].

        " ??? (rv isKindOf: Exception) ifTrue: [RemoteException new signal: (rv asString) ]."
        "numCurrentInvokes := numCurrentInvokes - 1.
        ((numCurrentInvokes == 0) and: [proxyDestroySemaphore isNil not]) ifTrue: [proxyDestroySemaphore signal]."
        ^ rv.
! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 11/9/2003 13:26'!
isDSO
        ^ true.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 2/1/2005 16:54'!
myRepAlg
        "Return my local replication algorithm instance (if I'm a replicated object)"
        ^ self proxyReceiver.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 21:25'!
proxyDestroy
        " Actually do the destruction "
        | tgt |
        tgt := target.

        " Wait until all the invocations have completed. "
        "Logger show: 'MessageCapture waiting... numCurrentInvokes=', numCurrentInvokes asString.
        [numCurrentInvokes == 0] whileFalse: [ proxyDestroySemaphore wait. ]. "

        target := nil.
        receiver := nil.
        Logger show: 'MessageCapture proxyDoDestroy from a : <<',(self class name),'>> to a <<', (tgt class name), '>>'.

        tgt class == MessageCapture
                ifTrue: [ self error: 'MessageCapture shouldn''t need to become another MessageCapture.' ]
                ifFalse: [self becomeForward: tgt].! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:28'!
proxyDestroySemaphore
        ^ proxyDestroySemaphore.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/3/2003 20:23'!
proxyIsProxy
        " A sure way to detect whether a class is being proxied. "
        ^ true.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:29'!
proxynumCurrentInvokes
        ^ numCurrentInvokes.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/4/2003 16:33'!
proxyReceiver
        ^ receiver! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:02'!
proxyReceiver: r
        receiver := r.
        receiver youAreMyReceiver: self.

        " Use this kind of like an initialize method too... "
        numCurrentInvokes := 0.
        proxyDestroySemaphore := Semaphore new.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:28'!
proxyReset
        " Should only be called in dire circumstances. E.g. from a debugger. "
        numCurrentInvokes := 0.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/3/2003 21:19'!
proxyTarget
        ^ target.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/21/2006 23:11'!
proxyTarget: aTarget
        "Set the proxy target. This is the object that used to be where this proxy is now."
        Logger show: 'MessageCapture targetting a ', (aTarget class name).
        (MessageCapture == aTarget class) ifTrue: [1 halt: 'bad'.].

        target ifNotNil: [ Logger show: 'MessageCapture Error: Proxy in use.'. ].
        "target assert: [target isDSO not]."
        target := aTarget.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/15/2006 22:05'!
inspect
        "Create and schedule an Inspector in which the user can examine the receiver's variables."

        MessageCaptureInspector openOn: self withEvalPane: true! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/15/2006 21:12'!
inspectorClass
        ^ MessageCaptureInspector.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/14/2006 09:06'!
isKindOf: aClass
        " Called by the debugger. Pretend that I'm a MessageCapture. "
        self class == aClass ifTrue: [^true].
        (self class inheritsFrom: aClass) ifTrue: [^true].
        ^ false.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
longPrintString
        ^ 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printOn: s
        s nextPutAll: 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printString
        "If you want to get really fancy, you can also ask the receiver to add something."
        ^ 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printStringLimitedTo: limit
        "Answer a String whose characters are a description of the receiver.
        If you want to print without a character limit, use fullPrintString."
        ^ 'a MessageCapture'.! !


!MessageCapture class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/21/2006 23:09'!
interceptFrom: anObject forwardTo: aReceivingObject
        " Install message capture on that object. This replaces anObject with a MessageCapture instance. "
        | aProxy |
       
        anObject isDSO ifTrue: [^anObject].

        "First we check to see if it's actually a valid object."
        (Array
                with: true
                with: false
                with: nil
                with: 1)
                do: [:i | (anObject isKindOf: i class)
                                ifTrue: [self halt]].
        (anObject isKindOf: Symbol)
                ifTrue: [self halt: 'bad idea.'].
        (anObject isKindOf: Metaclass)
                ifTrue: [self halt: 'tut tut!! Don''t install message capture on classes!!'].
        "(anObject isKindOf: ReplicationAlgorithm)
                ifTrue: [Logger show: 'Installing MessageCapture on a RepAlg...?']."
        (Smalltalk == anObject)
                ifTrue: [self halt: 'Proceed to seriously screw up your system.'].
               
        Logger show: 'Installing message capture on a ', anObject class asString.

        aProxy := self new.
        anObject become: aProxy.
        "Now keep in mind that magic has happened: anObject is now the proxy, aProxy is now anObject."
        anObject proxyTarget: aProxy.
        anObject proxyReceiver: aReceivingObject.
        ^ anObject! !

!MessageCapture class methodsFor: 'as yet unclassified' stamp: 'mvdg 10/8/2004 15:26'!
panic
        MessageCapture allInstances do: [ :m | m proxyDestroy ].! !


!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:15'!
inspectBasic
        self error: 'No can do.'.! !

!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 18:29'!
selection
        "The receiver has a list of variables of its inspected object.
        One of these is selected. Answer the value of the selected variable."
        | |
        selectionIndex = 0 ifTrue: [^ ''].
        selectionIndex = 1 ifTrue: [^ 'A ', object class name].
        selectionIndex = 2 ifTrue: [^ 'Look them up yourself, lazy bugger.'].
        " I need to be very careful about which methods I call. "
        selectionIndex = 3 ifTrue: [^ object proxyTarget ].
        selectionIndex = 4 ifTrue: [^ object proxyReceiver ].
        selectionIndex = 5 ifTrue: [^ object proxyDestroySemaphore ].
        selectionIndex = 6 ifTrue: [^ object proxynumCurrentInvokes ].
        ^ 'I don''t know.'.
! !

!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:54'!
viewerForValue
        self error: 'Don''t know how.'.! !


!MessageCaptureInspector class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 22:01'!
openAsMorphOn: anObject
        ^ self openAsMorphOn: anObject withLabel: ('A ', anObject class name asString).! !

!MessageCaptureInspector class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:59'!
openOn: anObject withEvalPane: withEval
        "Create and schedule an instance of me on the model, anInspector. "

        ^ self openOn: anObject withEvalPane: withEval withLabel: ('A ', anObject class name).! !


!RemoteException methodsFor: 'as yet unclassified' stamp: 'mvdg 2/4/2006 21:12'!
defaultAction
        ^ UnhandledError signalForException: self! !


!ProtoObject reorganize!
('testing' ifNil: ifNil:ifNotNil: ifNotNil: ifNotNil:ifNil: isInMemory isNil pointsTo:)
('comparing' identityHash == ~~)
('system primitives' become: cannotInterpret: doesNotUnderstand: nextInstance nextObject)
('objects from disk' rehash)
('debugging' doOnlyOnce: flag: rearmOneShot)
('initialize-release' initialize)
('apply primitives' tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with: tryPrimitive:withArgs:)
('should be in Squeak' becomeForward:)
!



Reply | Threaded
Open this post in threaded view
|

Re: [BUG] Crash while working on a Future.

Tom Phoenix
On 6/21/06, Michael van der Gulik <[hidden email]> wrote:

> I've got this really difficult bug.

I'll say; when you subclass ProtoObject, it's easy to forget about
some vital message that's usually handled in Object.

> When I execute the following, Squeak crashes:

> Future doing: [ByteStream].

What's a ByteStream? Do you mean ByteString?

You can debug the debugger to find where it crashes. Inspect the
debugger's model, then debug what happens when you send it #send.
That's how I found that the debugger chokes for the first time when a
MessageCapture is sent #class. If I fix that, it chokes for the second
time when it gets #instVarAt:. I'm sure there are more after that, but
I'll let you find them.

Cheers!

--Tom Phoenix

Reply | Threaded
Open this post in threaded view
|

Re: [BUG] Crash while working on a Future.

Michael van der Gulik
Hi Tom.

Attached is a change set with actually lets you use the debugger :-).
Remember to use this on a disposable image; the image won't be usable
after running this.

Run "Future doing: [ByteString]" to crash the image.

Tom Phoenix wrote:
> On 6/21/06, Michael van der Gulik <[hidden email]> wrote:
>
>> I've got this really difficult bug.
>
> I'll say; when you subclass ProtoObject, it's easy to forget about
> some vital message that's usually handled in Object.

Oh, I'm quite used to working with ProtoObject now. I've been capturing
messages for a couple of years now and doing crazy things with them.

>> When I execute the following, Squeak crashes:
>
>> Future doing: [ByteStream].
>
> What's a ByteStream? Do you mean ByteString?

Ahh... yea. One of those :o).

> You can debug the debugger to find where it crashes. Inspect the
> debugger's model, then debug what happens when you send it #send.
> That's how I found that the debugger chokes for the first time when a
> MessageCapture is sent #class. If I fix that, it chokes for the second
> time when it gets #instVarAt:. I'm sure there are more after that, but
> I'll let you find them.

Yea, I forgot to include all this stuff in the previous change set.

I have a forbidding feeling that the answer will lie in the virtual
machine implementation. Current I'm just trying to get
InterpreterSimulator working (from VMMaker-tpr-58), but that doesn't
seem to run. I've fixed the first few bugs, but then it wants to coerce
a Symbol (acting as a function pointer) to a long for inclusion in a
method cache...!?

Mikevdg.



'From Squeak3.8 of ''5 May 2005'' [latest update: #6665] on 22 June 2006 at 9:02:53 pm'!
ProtoObject subclass: #MessageCapture
        instanceVariableNames: 'target receiver proxyDestroySemaphore numCurrentInvokes'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!MessageCapture commentStamp: 'mikevdg 8/18/2003 10:18' prior: 0!
I intercept and forward messages going to another object.

To install me on an object:

Proxy interceptFrom: aTargetObject forwardTo: aReceivingObject

The receiving object can be any object, but must implement an "acceptFromProxy: theProxy message: aMessage" method.

Instance variables:
        target: the object I am capturing messages to.
        receiver: the object which is going to get those messages.

Don't forget about the return value from Intercepted messages!!

TODO: many methods are passed through to the target; this is not the desired behaviour.
TODO: there shouldn't be a target. Not all algorithms need it.!

Object subclass: #Future
        instanceVariableNames: 'semaphore messageCapture'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!Future commentStamp: 'mvdg 6/5/2006 19:59' prior: 0!
A future is an object which can be assigned as a return value, but doesn't become that value until that value has been computed by something else in the system. So it is a "future" in terms of a "future value". It lets code fork off a computation thread but still immediately return a value.

This class is actually only a manager. The actual object which will become: the value is a MessageCapture.!

Object subclass: #Logger
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!Logger commentStamp: 'mvdg 11/12/2005 08:50' prior: 0!
I just write log messages to a file. See my class methods.

This class is a quick hack and needs to be replaced with something more substantial.!

Logger class
        instanceVariableNames: 'outputFile doNotInterupt'!
Inspector subclass: #MessageCaptureInspector
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!
Exception subclass: #RemoteException
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DPON-kernel'!

!RemoteException commentStamp: 'mvdg 11/12/2005 08:23' prior: 0!
Currently used only in the MessageCapture class. !


!ProtoObject methodsFor: 'should be in Squeak' stamp: 'mvdg 6/21/2006 23:19'!
becomeForward: otherObject
        "Primitive. All variables in the entire system that used to point
        to the receiver now point to the argument.
        Fails if either argument is a SmallInteger."

        (Array with: self)
                elementsForwardIdentityTo:
                        (Array with: otherObject)! !

!ProtoObject methodsFor: 'should be in Squeak' stamp: 'mvdg 6/22/2006 20:56'!
instVarAt: index
        "Primitive. Answer a fixed variable in an object. The numbering of the
        variables corresponds to the named instance variables. Fail if the index
        is not an Integer or is not the index of a fixed variable. Essential. See
        Object documentation whatIsAPrimitive."

        <primitive: 73>
        "Access beyond fixed variables."
        ^self basicAt: index - self class instSize ! !

!ProtoObject methodsFor: 'should be in Squeak' stamp: 'mvdg 6/22/2006 20:56'!
instVarAt: anInteger put: anObject
        "Primitive. Store a value into a fixed variable in the receiver. The
        numbering of the variables corresponds to the named instance variables.
        Fail if the index is not an Integer or is not the index of a fixed variable.
        Answer the value stored as the result. Using this message violates the
        principle that each object has sovereign control over the storing of
        values into its instance variables. Essential. See Object documentation
        whatIsAPrimitive."

        <primitive: 74>
        "Access beyond fixed fields"
        ^self basicAt: anInteger - self class instSize put: anObject! !


!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/22/2006 21:00'!
class
        "Primitive. Answer the object which is the receiver's class. Essential. See
        Object documentation whatIsAPrimitive."

        <primitive: 111>
        self primitiveFailed! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/21/2006 22:55'!
doesNotUnderstand: m
        " doesNotUnderstand gets called on every message without a method. Every message thus. "
        | rv |
        " If you get a doesNotUnderstand in the line below, look further down the stack.
         Also, try calling proxyReset on your replicated object. "
        "numCurrentInvokes := numCurrentInvokes + 1.
        (numCurrentInvokes > 10) ifTrue: [ RemoteException new signal: 'MessageCapture possibly in recursive loop.' ]. "

        m lookupClass: nil.
        Logger show: 'MessageCapture got message: <<', (m asString), '>>'.
        receiver ifNil: [RemoteException new signal: 'MessageCapture has no receiver.'].

        [ rv := receiver acceptFromProxy: self message: m ]
                ifError: [ :e :r |
                        "numCurrentInvokes := numCurrentInvokes - 1."
                        Logger show: 'MessageCapture got error: ', (e asString).].

        " ??? (rv isKindOf: Exception) ifTrue: [RemoteException new signal: (rv asString) ]."
        "numCurrentInvokes := numCurrentInvokes - 1.
        ((numCurrentInvokes == 0) and: [proxyDestroySemaphore isNil not]) ifTrue: [proxyDestroySemaphore signal]."
        ^ rv.
! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 11/9/2003 13:26'!
isDSO
        ^ true.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 2/1/2005 16:54'!
myRepAlg
        "Return my local replication algorithm instance (if I'm a replicated object)"
        ^ self proxyReceiver.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 21:25'!
proxyDestroy
        " Actually do the destruction "
        | tgt |
        tgt := target.

        " Wait until all the invocations have completed. "
        "Logger show: 'MessageCapture waiting... numCurrentInvokes=', numCurrentInvokes asString.
        [numCurrentInvokes == 0] whileFalse: [ proxyDestroySemaphore wait. ]. "

        target := nil.
        receiver := nil.
        Logger show: 'MessageCapture proxyDoDestroy from a : <<',(self class name),'>> to a <<', (tgt class name), '>>'.

        tgt class == MessageCapture
                ifTrue: [ self error: 'MessageCapture shouldn''t need to become another MessageCapture.' ]
                ifFalse: [self becomeForward: tgt].! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:28'!
proxyDestroySemaphore
        ^ proxyDestroySemaphore.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/3/2003 20:23'!
proxyIsProxy
        " A sure way to detect whether a class is being proxied. "
        ^ true.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:29'!
proxynumCurrentInvokes
        ^ numCurrentInvokes.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/4/2003 16:33'!
proxyReceiver
        ^ receiver! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:02'!
proxyReceiver: r
        receiver := r.
        receiver youAreMyReceiver: self.

        " Use this kind of like an initialize method too... "
        numCurrentInvokes := 0.
        proxyDestroySemaphore := Semaphore new.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/18/2006 18:28'!
proxyReset
        " Should only be called in dire circumstances. E.g. from a debugger. "
        numCurrentInvokes := 0.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mikevdg 7/3/2003 21:19'!
proxyTarget
        ^ target.! !

!MessageCapture methodsFor: 'proxy' stamp: 'mvdg 6/21/2006 23:11'!
proxyTarget: aTarget
        "Set the proxy target. This is the object that used to be where this proxy is now."
        Logger show: 'MessageCapture targetting a ', (aTarget class name).
        (MessageCapture == aTarget class) ifTrue: [1 halt: 'bad'.].

        target ifNotNil: [ Logger show: 'MessageCapture Error: Proxy in use.'. ].
        "target assert: [target isDSO not]."
        target := aTarget.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/22/2006 21:00'!
basicAt: index
        "Primitive. Assumes receiver is indexable. Answer the value of an
        indexable element in the receiver. Fail if the argument index is not an
        Integer or is out of bounds. Essential. Do not override in a subclass. See
        Object documentation whatIsAPrimitive."

        <primitive: 60>
        index isInteger ifTrue: [self errorSubscriptBounds: index].
        index isNumber
                ifTrue: [^self basicAt: index asInteger]
                ifFalse: [self errorNonIntegerIndex]! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/22/2006 21:01'!
basicAt: index put: value
        "Primitive. Assumes receiver is indexable. Store the second argument
        value in the indexable element of the receiver indicated by index. Fail
        if the index is not an Integer or is out of bounds. Or fail if the value is
        not of the right type for this kind of collection. Answer the value that
        was stored. Essential. Do not override in a subclass. See Object
        documentation whatIsAPrimitive."

        <primitive: 61>
        index isInteger
                ifTrue: [(index >= 1 and: [index <= self size])
                                        ifTrue: [self errorImproperStore]
                                        ifFalse: [self errorSubscriptBounds: index]].
        index isNumber
                ifTrue: [^self basicAt: index asInteger put: value]
                ifFalse: [self errorNonIntegerIndex]! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/15/2006 22:05'!
inspect
        "Create and schedule an Inspector in which the user can examine the receiver's variables."

        MessageCaptureInspector openOn: self withEvalPane: true! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/15/2006 21:12'!
inspectorClass
        ^ MessageCaptureInspector.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/14/2006 09:06'!
isKindOf: aClass
        " Called by the debugger. Pretend that I'm a MessageCapture. "
        self class == aClass ifTrue: [^true].
        (self class inheritsFrom: aClass) ifTrue: [^true].
        ^ false.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
longPrintString
        ^ 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printOn: s
        s nextPutAll: 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printString
        "If you want to get really fancy, you can also ask the receiver to add something."
        ^ 'a MessageCapture'.! !

!MessageCapture methodsFor: 'object functionality' stamp: 'mvdg 6/18/2006 18:33'!
printStringLimitedTo: limit
        "Answer a String whose characters are a description of the receiver.
        If you want to print without a character limit, use fullPrintString."
        ^ 'a MessageCapture'.! !


!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:33'!
acceptFromProxy: p message: m
        " Make the calling process wait. "
        Logger show: 'Future got: ', (m selector).
        semaphore wait.
        Logger show: 'Future doing: ', (m selector), 'on a ', (self class name).
        ^ messageCapture perform: (m selector) withArguments: (m arguments).
! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/21/2006 23:16'!
doing: aBlock
        | |
        messageCapture := MessageCapture new.
        messageCapture proxyReceiver: self.

        [ | target |
                target := aBlock value. " <--- This is the main part of the Future "
                messageCapture proxyTarget: target.
                Logger show: 'Future resolving to a ', (target class name).
                (ByteString == target) ifTrue: [self halt: 'about to crash.'].
          messageCapture proxyDestroy.
                Logger show: 'Future resolved to a ', (messageCapture class name asString).
          [semaphore isSignaled] whileFalse: [ semaphore signal ].
        ] fork.

        ^ messageCapture.! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:29'!
initialize
        semaphore := Semaphore new.
! !

!Future methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:56'!
youAreMyReceiver: bob! !


!Future class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 17:27'!
doing: aBlock
        ^ self new doing: aBlock.! !


!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 11/12/2005 09:00'!
close
        outputFile close.! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:01'!
ensureOpen
        outputFile ifNil: [
                outputFile := CrLfFileStream new open: (SmalltalkImage current imageName,'.log') forWrite: true.
        ] ifNotNil: [
                outputFile ensureOpen.
                outputFile setToEnd.
        ].
        doNotInterupt ifNil: [ doNotInterupt := Semaphore forMutualExclusion ].! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 11/12/2005 09:11'!
reset
        outputFile := nil.! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:12'!
show: aMessage
        self ensureOpen.
        doNotInterupt critical: [
                outputFile nextPutAll: (Time now asString). "Probably better done using Streams..."
                outputFile nextPutAll: ' ',aMessage.
                (aMessage includes: (Character cr)) ifFalse: [
                        outputFile nextPut: Character cr. ].
        ]
! !

!Logger class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/14/2006 09:03'!
stackTrace
        | sender |
        self ensureOpen.
        doNotInterupt critical: [
                self show: 'Stack trace follows --------------'.
                sender := thisContext sender.
                [ sender isNil ] whileFalse: [
                        sender printOn: outputFile.
                        outputFile cr.
                        sender := sender sender.
                ].
        ]
! !


!MessageCapture class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/21/2006 23:09'!
interceptFrom: anObject forwardTo: aReceivingObject
        " Install message capture on that object. This replaces anObject with a MessageCapture instance. "
        | aProxy |
       
        anObject isDSO ifTrue: [^anObject].

        "First we check to see if it's actually a valid object."
        (Array
                with: true
                with: false
                with: nil
                with: 1)
                do: [:i | (anObject isKindOf: i class)
                                ifTrue: [self halt]].
        (anObject isKindOf: Symbol)
                ifTrue: [self halt: 'bad idea.'].
        (anObject isKindOf: Metaclass)
                ifTrue: [self halt: 'tut tut!! Don''t install message capture on classes!!'].
        "(anObject isKindOf: ReplicationAlgorithm)
                ifTrue: [Logger show: 'Installing MessageCapture on a RepAlg...?']."
        (Smalltalk == anObject)
                ifTrue: [self halt: 'Proceed to seriously screw up your system.'].
               
        Logger show: 'Installing message capture on a ', anObject class asString.

        aProxy := self new.
        anObject become: aProxy.
        "Now keep in mind that magic has happened: anObject is now the proxy, aProxy is now anObject."
        anObject proxyTarget: aProxy.
        anObject proxyReceiver: aReceivingObject.
        ^ anObject! !

!MessageCapture class methodsFor: 'as yet unclassified' stamp: 'mvdg 10/8/2004 15:26'!
panic
        MessageCapture allInstances do: [ :m | m proxyDestroy ].! !


!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:15'!
inspectBasic
        self error: 'No can do.'.! !

!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/18/2006 18:29'!
selection
        "The receiver has a list of variables of its inspected object.
        One of these is selected. Answer the value of the selected variable."
        | |
        selectionIndex = 0 ifTrue: [^ ''].
        selectionIndex = 1 ifTrue: [^ 'A ', object class name].
        selectionIndex = 2 ifTrue: [^ 'Look them up yourself, lazy bugger.'].
        " I need to be very careful about which methods I call. "
        selectionIndex = 3 ifTrue: [^ object proxyTarget ].
        selectionIndex = 4 ifTrue: [^ object proxyReceiver ].
        selectionIndex = 5 ifTrue: [^ object proxyDestroySemaphore ].
        selectionIndex = 6 ifTrue: [^ object proxynumCurrentInvokes ].
        ^ 'I don''t know.'.
! !

!MessageCaptureInspector methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:54'!
viewerForValue
        self error: 'Don''t know how.'.! !


!MessageCaptureInspector class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 22:01'!
openAsMorphOn: anObject
        ^ self openAsMorphOn: anObject withLabel: ('A ', anObject class name asString).! !

!MessageCaptureInspector class methodsFor: 'as yet unclassified' stamp: 'mvdg 6/15/2006 21:59'!
openOn: anObject withEvalPane: withEval
        "Create and schedule an instance of me on the model, anInspector. "

        ^ self openOn: anObject withEvalPane: withEval withLabel: ('A ', anObject class name).! !


!RemoteException methodsFor: 'as yet unclassified' stamp: 'mvdg 2/4/2006 21:12'!
defaultAction
        ^ UnhandledError signalForException: self! !

Object removeSelector: #instVarAt:!
Object removeSelector: #instVarAt:put:!

!ProtoObject reorganize!
('testing' ifNil: ifNil:ifNotNil: ifNotNil: ifNotNil:ifNil: isInMemory isNil pointsTo:)
('comparing' identityHash == ~~)
('system primitives' become: cannotInterpret: doesNotUnderstand: nextInstance nextObject)
('objects from disk' rehash)
('debugging' doOnlyOnce: flag: rearmOneShot)
('initialize-release' initialize)
('apply primitives' tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with: tryPrimitive:withArgs:)
('should be in Squeak' becomeForward: instVarAt: instVarAt:put:)
!



Reply | Threaded
Open this post in threaded view
|

Re: [BUG] Crash while working on a Future.

Tom Phoenix
On 6/22/06, Michael van der Gulik <[hidden email]> wrote:

> Attached is a change set with actually lets you use the debugger :-).

Thanks. This gave me a better idea about what you were trying to do, I think.

> Run "Future doing: [ByteString]" to crash the image.

Does it have to be ByteString? That's an important class in the
system. It seems to be an arbitrary choice, since it only shows up in
your halt code.

As I understand the operation of #becomeForward:, you're
instantaneously turning your MessageCapture instance into ByteString.
But the identity hash of your instance should stay the same, in case
it's in a dictionary, say. That means that the identity hash of
ByteString changes to become the hash of your object, even though that
can break any dictionaries that *it* was in.

So it would probably break even if you didn't use such an important
class as ByteString.

I'm not sure of the proper way to deal with identity hash invariance
when using #becomeForward:. We're into some serious magic here.
Object>>becomeForward:copyHash: seems to have been implemented to deal
with this sort of thing; though there may be further pitfalls.

Maybe a subclass of ProtoObject and wizardly uses of #become: isn't
the technology you need to use. Have I understood correctly what
you're trying to do? It seems to me that you want an object that is a
placeholder today and the real value tomorrow. That's probably
possible without much magic. But you must ensure that a client doesn't
send messages to the placeholder before it becomes the real value. I
don't see any good way to do that without asking the client to do
something active, such as using a Semaphore, or sending the
placeholder a signal. The trouble I see is that there's no way to tell
when a message to the placeholder is "really" destined for the real
value.

Here's some brainstorming....

  "A client wants to do something while a calculation completes"
  nilNowValLater := Future doing: [ 49 sqrt ].
  nilNowValLater inspect. "It's nil, maybe still"
  self doSomething.
  [ nilNowValLater isNil ] whileTrue. "Bad: Busy wait"
  nilNowValLater inspect. "Now it's 7"

The busy wait is undesirable, since we tie up the processor precisely
when we want it to devote time to the other process. We could put in a
Delay, but let's try a different way.

  stat := Future doing: [ 49 sqrt ].
  stat inspect. "It's an instance of Future"
  self doSomething.
  stat inspect. "It's still an instance of Future"
  stat wait. "stat updates, waiting if needed"
  stat inspect. "Now it's 7"

Now there's no busy wait. In this scheme, Future gives a status object
that can (presumably) be interrogated about the ongoing process, i.e,
whether it's complete or still running. And the status object doesn't
auto-convert; you have to send it #wait. (Because 7 won't necessarily
respond correctly to #wait, at least.) But why do this with a
magically-updating value, since it doesn't even auto-convert? Subtract
the magic and you get this:

  stat := Future doing: [ 49 sqrt ].
  stat inspect. "It's an instance of Future"
  self doSomething.
  stat isDone ifFalse: [ self doSomethingMore ].
  stat result inspect. "gives 7"

Under this metaphor, an instance of Future is the status of the
request, and you have to ask it for the result. Ask whenever you'd
like, but you may have to wait for the answer, if it's not ready yet.
Nothing magically updates, so I don't have to deal with any new
concepts. And it allows code like this:

  "Open an inspector as soon as the network request has returned a value."
  stat := Future doing: [ networkRequest value ].
  stat whenDoneDo: [:result | result inspect ].

  "If time runs out, return a failureToken"
  stat afterTimeoutSeconds: 300 terminateAndDo: [ stat return: self
failureToken ].

  "Another way to handle time-out: cancel the inspection"
  stat afterTimeoutSeconds: 300 terminateAndDo: [ stat abort ].

This seems like a fairly clean and flexible model that would be
relatively easy to implement. Would something like that work for you?

Hope this helps!

--Tom Phoenix

Reply | Threaded
Open this post in threaded view
|

Re: [BUG] Crash while working on a Future.

Michael van der Gulik
Tom Phoenix wrote:

> On 6/22/06, Michael van der Gulik <[hidden email]> wrote:
>
>> Attached is a change set with actually lets you use the debugger :-).
>
>
> Thanks. This gave me a better idea about what you were trying to do, I
> think.
>
>> Run "Future doing: [ByteString]" to crash the image.
>
>
> Does it have to be ByteString? That's an important class in the
> system. It seems to be an arbitrary choice, since it only shows up in
> your halt code.
>
> As I understand the operation of #becomeForward:, you're
> instantaneously turning your MessageCapture instance into ByteString.
> But the identity hash of your instance should stay the same, in case
> it's in a dictionary, say. That means that the identity hash of
> ByteString changes to become the hash of your object, even though that
> can break any dictionaries that *it* was in.
>
> So it would probably break even if you didn't use such an important
> class as ByteString.

I believe that you may have hit the nail on the head. I'm still trying
to work out exactly how the hashes do and should work. Many thanks! That
would explain why Smalltalk the SystemDictionary got all confused in my
earlier attempts.

As with the rest of your email; I'll have to read it tomorrow (no time).
You are right in that using ProtoObject is a bit over the top. Also, my
implementation is probably too slow; #become: in Squeak is a slow
operation and I'm using it a bit too much.

Mikevdg.


Reply | Threaded
Open this post in threaded view
|

Re: [BUG] Crash while working on a Future.

Michael van der Gulik
In reply to this post by Tom Phoenix
Hi Tom (and others)

Tom Phoenix wrote:

>> Run "Future doing: [ByteString]" to crash the image.
>
>
> Does it have to be ByteString? That's an important class in the
> system. It seems to be an arbitrary choice, since it only shows up in
> your halt code.

It was arbitrary; it was the only case I tried because it was the one
that I originally found to crash the image.

> As I understand the operation of #becomeForward:, you're
> instantaneously turning your MessageCapture instance into ByteString.
> But the identity hash of your instance should stay the same, in case
> it's in a dictionary, say. That means that the identity hash of
> ByteString changes to become the hash of your object, even though that
> can break any dictionaries that *it* was in.

I consider that a bug in becomeForward:. I've just done some
experimentation:

a := Object new.
b := Object new.
a hash   414
b hash   1169
a becomeForward: b copyHash: true.
a hash  414
b hash  414  "b's hash just got clobbered"

a := Object new.
b := Object new.
a hash  966
b hash  1534
a becomeForward: b copyHash: false.
a hash  1534  "IMO this should be the default."
b hash  1534

a := Object new.
b := Object new.
a hash  2248
b hash  2955
a becomeForward: b.
a hash  2248
b hash  2248  "b's hash just got clobbered"

So it seems by default that #becomeForward: copys the hash by default.

In my opinion, this is very broken. I use becomeForward:, assuming that
every reference to "a" really does become a reference to "b" and that
"b" won't suddenly have any unexpected state changes (ESPECIALLY a
change in hash!).

My code shouldn't break (when it's bug free :-) ) because if "a" gets
included in any dictionaries, a dictionary implementation will send the
#hash message, which will wait on the semaphore in doesNotUnderstand: in
"a" until "a" has becomeForwarded to "b". The #hash message should then
return with the correct hash.

I assume, of course, that becomeForward: will also change any references
in MethodContexts correctly.

> Maybe a subclass of ProtoObject and wizardly uses of #become: isn't
> the technology you need to use. Have I understood correctly what
> you're trying to do? It seems to me that you want an object that is a
> placeholder today and the real value tomorrow.

Yup. It's called a "future" :-). If I remember correctly, its a pattern
used for concurrent applications, although evidently not a simple one to
implement.


> That's probably
> possible without much magic. But you must ensure that a client doesn't
> send messages to the placeholder before it becomes the real value. I
> don't see any good way to do that without asking the client to do
> something active, such as using a Semaphore, or sending the
> placeholder a signal. The trouble I see is that there's no way to tell
> when a message to the placeholder is "really" destined for the real
> value.

I agree that a better and more robust solution would not involve using
ProtoObjects. Infact, an even better solution would be to look at the
bigger picture and try not to use Futures at all.

>
> Here's some brainstorming....
>
>  "A client wants to do something while a calculation completes"
>  nilNowValLater := Future doing: [ 49 sqrt ].
>  nilNowValLater inspect. "It's nil, maybe still"
>  self doSomething.
>  [ nilNowValLater isNil ] whileTrue. "Bad: Busy wait"
>  nilNowValLater inspect. "Now it's 7"
>
> The busy wait is undesirable, since we tie up the processor precisely
> when we want it to devote time to the other process. We could put in a
> Delay, but let's try a different way.

A semaphore would be better here.

>  stat := Future doing: [ 49 sqrt ].
>  stat inspect. "It's an instance of Future"
>  self doSomething.
>  stat isDone ifFalse: [ self doSomethingMore ].
>  stat result inspect. "gives 7"

This is a better way of doing things.

> Under this metaphor, an instance of Future is the status of the
> request, and you have to ask it for the result. Ask whenever you'd
> like, but you may have to wait for the answer, if it's not ready yet.
> Nothing magically updates, so I don't have to deal with any new
> concepts. And it allows code like this:
>
>  "Open an inspector as soon as the network request has returned a value."
>  stat := Future doing: [ networkRequest value ].
>  stat whenDoneDo: [:result | result inspect ].
>
>  "If time runs out, return a failureToken"
>  stat afterTimeoutSeconds: 300 terminateAndDo: [ stat return: self
> failureToken ].
>
>  "Another way to handle time-out: cancel the inspection"
>  stat afterTimeoutSeconds: 300 terminateAndDo: [ stat abort ].
>
> This seems like a fairly clean and flexible model that would be
> relatively easy to implement. Would something like that work for you?

Potentially; in either case it is certainly a very useful concept. I'm
going to have to refactor my code anyway, so I'll see if I can get rid
of my Futures altogether.

> Hope this helps!

Mentioning the hash stuff certainly did! Many thanks for that.

Mikevdg.