Skip to main content
Engineering LibreTexts

12.17: Ensure's Implementation

  • Page ID
    45987
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Now we propose to have a look at the implementation of the method ensure:.

    First we need to understand how the unwind block is stored and how this information is found at run-time. Let’s look at the definition of the central method ensure: defined on the class BlockClosure.

    ensure: aBlock
        "Evaluate a termination block after evaluating the receiver, regardless of
        whether the receiver's evaluation completes. N.B. This method is *not*
        implemented as a primitive. Primitive 198 always fails. The VM uses prim
        198 in a context's method as the mark for an ensure:/ifCurtailed: activation."
        
        | complete returnValue |
        <primitive: 198>
        returnValue := self valueNoContextSwitch.
        complete ifNil: [
            complete := true.
            aBlock value ].
        ^ returnValue
    

    The <primitive: 198 > works the same way as the <primitive: 199 > we saw in the previous section. It always fails, however, its presence marks the method in way that can easily be detected from the context activating this method. Moreover, the unwind block is stored the same way as the exception class and its associated handler. More explicitly, it is stored in the context of ensure: method execution, that can be accessed from the block through thisContext sender tempAt: 1.

    In the case where the block does not fail and does not have a non-local return, the ensure: message implementation executes the block, stores the result in the returnValue variable, executes the argument block and lastly returns the result of the block previously stored. The complete variable is here to prevent the argument block from being executed twice.

    Ensuring a failing block. The ensure: message will execute the argument block even if the block fails. In the following example, the ensureWithOnDo message returns 2 and executes 1. In the subsequent section we will carefully look at where and what the block is actually returning and in which order the blocks are executed.

    Bexp>>ensureWithOnDo
        ^[ [ Error signal ] ensure: [ 1 ].
            ^3 ] on: Error do: [ 2 ]
    

    Let’s have a quick example before looking at the implementation. We define 4 blocks and 1 method that ensure a failing Block.

    Bexp>>mainBlock
        ^[ self traceCr: 'mainBlock start'.
        self failingBlock ensure: self ensureBlock.
        self traceCr: 'mainBlock end' ]
    
    Bexp>>failingBlock
        ^[ self traceCr: 'failingBlock start'.
        Error signal.
        self traceCr: 'failingBlock end' ]
    
    Bexp>>ensureBlock
        ^[ self traceCr: 'ensureBlock value'.
        #EnsureBlockValue ]
    
    Bexp>>exceptionHandlerBlock
        ^[ self traceCr: 'exceptionHandlerBlock value'.
            #ExceptionHandlerBlockValue ]
    
    Bexp>>start
        | res |
        self traceCr: 'start start'.
        res := self mainBlock on: Error do: self exceptionHandlerBlock.
        self traceCr: 'start end'.
        self traceCr: 'The result is : ', res, '.'.
        ^ res 
    

    Executing Bexp new start prints the following (we added indentation to stress the calling flow).

    start start
        mainBlock start
            failingBlock start
                exceptionHandlerBlock value
                ensureBlock value
    start end
    The result is: ExceptionHandlerBlockValue. 
    

    There are three important things to see. First, the failing block and the main block are not fully executed because of the signal message. Secondly, the exception block is executed before the ensure block. Lastly, the start method will return the result of the exception handler block.

    To understand how this works, we have to look at the end of the exception implementation. We finish the previous explanation on the handleSignal method.

    ContextPart»handleSignal: exception
        "Sent to handler (on:do:) contexts only. If my exception class (first arg) handles
            exception then execute my handle block (second arg), otherwise forward this
            message to the next handler context. If none left, execute exception's defaultAction
            (see nil>>handleSignal:)."
    
        | value |
        ((self exceptionClass handles: exception)
        and: [self exceptionHandlerIsActive])
            ifFalse: [ ^ self nextHandlerContext handleSignal: exception ].
    
        exception privHandlerContext: self contextTag.
        "disable self while executing handle block"
        self exceptionHandlerIsActive: false.
        value := [ self exceptionHandlerBlock cull: exception ]
            ensure: [ self exceptionHandlerIsActive: true ].
        "return from self if not otherwise directed in handle block"
        self return: value.
    

    In our example, Pharo will execute the failing block, then will look for the next handler context, marked with <primitive: 199 >. As a regular exception, Pharo finds the exception handler context, and runs the exceptionHandlerBlock. The method handleSignal finishes with the return: method. Let’s have a look into it.

    ContextPart>>return: value
        "Unwind thisContext to self and return value to self's sender. Execute any unwind
            blocks while unwinding. ASSUMES self is a sender of thisContext"
            
        sender ifNil: [self cannotReturn: value to: sender].
        sender resume: value 
    

    The return: message will check if the context has a sender, and, if not, send a CannotReturn Exception. Then the sender of this context will call the resume: message.

    resume: value
        "Unwind thisContext to self and resume with value as result of last send. Execute
            unwind blocks when unwinding. ASSUMES self is a sender of thisContext"
        
        self resume: value through: (thisContext findNextUnwindContextUpTo: self)
    
    ContextPart>>resume: value through: firstUnwindContext
        "Unwind thisContext to self and resume with value as result of last send.
        Execute any unwind blocks while unwinding.
        ASSUMES self is a sender of thisContext."
        
        | context unwindBlock |
        self isDead
            ifTrue: [ self cannotReturn: value to: self ].
        context := firstUnwindContext.
        [ context isNil ] whileFalse: [
            context unwindComplete ifNil:[
                context unwindComplete: true.
                unwindBlock := context unwindBlock.
                thisContext terminateTo: context.
                unwindBlock value].
            context := context findNextUnwindContextUpTo: self].
        thisContext terminateTo: self.
        ^value
    

    This is the method where the argument block of ensure: is executed. This method looks for all the unwind contexts between the context of the method resume: and self, which is the sender of the on:do: context (in our case the context of start). When the method finds an unwound context, the unwound block is executed. Lastly, it triggers the terminateTo: message.

    ContextPart>>terminateTo: previousContext
        "Terminate all the Contexts between me and previousContext, if previousContext is on
            my Context stack. Make previousContext my sender."
        
        | currentContext sendingContext |
        <primitive: 196>
        (self hasSender: previousContext) ifTrue: [
            currentContext := sender.
            [currentContext == previousContext] whileFalse: [
                sendingContext := currentContext sender.
                currentContext terminate.
                currentContext := sendingContext]].
        sender := previousContext
    

    Basically, this method terminates all the contexts between thisContext and self, which is the sender of the on:do: context (in our case the context of start). Moreover, the sender of thisContext will become self, which is the sender of the on:do: context (in our case the context of start). It is implemented as a primitive for performance only, so the primitive is optional and the fallback code has the same behavior.

    Let’s summarize what happens with Figure \(\PageIndex{1}\) which represents the execution of the method ensureWithOnDo defined previously.

    Context stack diagram.
    Figure \(\PageIndex{1}\): Context stack.
    The previous figure's legend.
    Figure \(\PageIndex{2}\): Legend of the previous figure.

    Ensuring a non local return. The method resume:through: is also called when performing a non local return. In the case of non local return, the stack is unwound in a similar way than or exception. The virtual machine, while performing a non local return, send the message aboutToReturn:through: to the active context. Therefore, if one has changed the implementation of exception in the language,


    This page titled 12.17: Ensure's Implementation is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.