Search code examples
jolie

Why is the installed exception handler not called directly in Jolie?


I have a scenario where a want to refresh a resource, but I also want to be able to terminate the refresh.

I have the following interfaces:

interface terminate{
   OneWay: terminate(void)
}

interface refreshAll {
    RequestResponse: refreshAll(void)(void)
}

And the resource:

include "interface.iol"
include "console.iol"

inputPort dummyInput {
   Location: "socket://localhost:8002"
   Protocol: sodep
   Interfaces: refreshAll
}


init{
    registerForInput@Console()()
}

main 
{
    refreshAll( number )( result ) {
        println@Console("refresh")();
        in(req);
        result = void
    }

}

And the service I run if I want to terminate:

include "interface.iol"

outputPort term {
   Location: "socket://localhost:8000"
   Protocol: sodep
   Interfaces: terminate
}

main 
{
    terminate@term()
}

And the program coordinating everything:

include "interface.iol"
include "console.iol"

inputPort terminate {
Location: "socket://localhost:8000"
              Protocol: sodep
              Interfaces: terminate 
}

outputPort resource {
Location: "socket://localhost:8002"
              Protocol: sodep
              Interfaces: refreshAll
}

main 
{
    scope(hej){
        install(
            hello => {
                println@Console("terminate")()
            }
        );

            {
                refreshAll@resource()()
            }|
            {  
                terminate();
                throw(hello)
            }

    }
}

Why is the exception not thrown directly when terminate is received?

That is, in the coordination program the exception handler is not called when terminate is recived. The exception handler is first invoked after the refreshAll@resource()() has finished.

How can I write so the refreshAllis terminated getting a terminate?


Solution

  • In Jolie, a fault (what you're triggering with the throw primitive) does not interrupt a pending solicit-response call (your refreshAll@resource()()): if it hasn't started yet, it's not started at all, but if the request has been sent to the intended receiver (resource here), then Jolie is going to wait for the response (or a timeout) before propagating the fault to the enclosing scope (hej here). That's because the result of the solicit response might be important for the fault management logic.

    If you don't care about the result of the solicit-response in your fault handler (and here you don't), then you can just make a little adapter to handle the solicit-response call in two steps, effectively making it asynchronous at the application level (at the implementation level, most Jolie stuff is asynchronous anyway, but here you want to explicitly see that the communication happens in two steps in your program logic).

    I modified your coordinator program as follows and then everything worked as you wanted:

    include "interface.iol"
    include "console.iol"
    
    inputPort terminate {
    Location: "socket://localhost:8000"
    Protocol: sodep
    Interfaces: terminate
    }
    
    outputPort resource {
    Location: "socket://localhost:8002"
    Protocol: sodep
    Interfaces: refreshAll
    }
    
    // New stuff from here
    interface RefreshAsyncAdapterIface {
    OneWay:
        refreshAllAsync(void)
    }
    
    interface RefreshAsyncClientIface {
    OneWay:
        refreshCompleted(void)
    }
    
    inputPort CoordinatorInput {
    Location: "local://Coordinator"
    Interfaces: RefreshAsyncClientIface
    }
    
    outputPort Coordinator {
    Location: "local://Coordinator"
    Interfaces: RefreshAsyncClientIface
    }
    
    // Adapter service to split the solicit-response in two steps
    service RefreshAsyncAdapter {
    Interfaces: RefreshAsyncAdapterIface
        main {
            refreshAllAsync();
            refreshAll@resource()();
            refreshCompleted@Coordinator()
        }
    }
    
    main
    {
        scope(hej){
            install(
                hello => {
                    println@Console("terminate")()
                }
            );
    
                {
                    // Split the call in send and receive steps
                    refreshAllAsync@RefreshAsyncAdapter();
                    refreshCompleted()
                }|
                {
                    terminate();
                    throw(hello)
                }
    
        }
    }
    

    This patterns appears quite often, so we'll probably make it even easier in the future.

    References: