From f854211c0ae178f4b2adf8ab60a2712c6345cb8a Mon Sep 17 00:00:00 2001 From: David Grove Date: Mon, 21 Mar 2022 21:24:37 -0400 Subject: [PATCH] mostly require caller to be specified on actor calls (#281) --- .../kar/example/philosophers/Cafe.java | 4 +- .../kar/example/philosophers/Philosopher.java | 8 ++-- .../kar/example/philosophers/Cafe.java | 4 +- .../kar/example/philosophers/Philosopher.java | 8 ++-- examples/actors-dp-js/philosophers.js | 12 ++--- examples/actors-ykt/ykt.js | 2 +- .../research/kar/example/timeout/Test.java | 5 +- .../timeout/middle/MiddleServices.java | 4 +- .../main/java/com/ibm/research/kar/Kar.java | 47 +++++-------------- .../main/java/com/ibm/research/kar/Kar.java | 25 +++------- .../research/kar/quarkus/ActorEndpoints.java | 11 +++-- sdk-js/index.d.ts | 2 +- sdk-js/index.js | 17 +++++++ 13 files changed, 67 insertions(+), 82 deletions(-) diff --git a/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java b/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java index 20f6cfd9..5fa59592 100644 --- a/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java +++ b/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java @@ -34,7 +34,7 @@ public class Cafe extends ActorSkeleton { @Remote public Uni occupancy(JsonString table) { - return Actors.call(Actors.ref("Table", table.getString()), "occupancy"); + return Actors.call(this, Actors.ref("Table", table.getString()), "occupancy"); } @Remote @@ -50,7 +50,7 @@ public Uni seatTable(JsonNumber n, JsonNumber servings) { @Remote public Uni seatTable(JsonNumber n, JsonNumber servings, JsonString requestId) { - return Actors.call(Actors.ref("Table", requestId.getString()), "prepare", Json.createValue(this.getId()), n, servings) + return Actors.call(this, Actors.ref("Table", requestId.getString()), "prepare", Json.createValue(this.getId()), n, servings) .chain(() -> Uni.createFrom().item(requestId)); } } diff --git a/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java b/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java index 4f1c46dd..98256275 100644 --- a/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java +++ b/examples/actors-dp-java-reactive/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java @@ -95,7 +95,7 @@ public Uni joinTable(JsonString table, JsonString firstFork, JsonStrin @Remote public Uni getFirstFork(JsonNumber attempt) { - return Actors.call(Actors.ref("Fork", this.firstFork.getString()), "pickUp", Json.createValue(this.getId())) + return Actors.call(this, Actors.ref("Fork", this.firstFork.getString()), "pickUp", Json.createValue(this.getId())) .chain(acquired -> { if (acquired.equals(JsonValue.TRUE)) { return Actors.tailCall(this, "getSecondFork", Json.createValue(1)); @@ -111,7 +111,7 @@ public Uni getFirstFork(JsonNumber attempt) { @Remote public Uni getSecondFork(JsonNumber attempt) { - return Actors.call(Actors.ref("Fork", this.secondFork.getString()), "pickUp", Json.createValue(this.getId())) + return Actors.call(this, Actors.ref("Fork", this.secondFork.getString()), "pickUp", Json.createValue(this.getId())) .chain(acquired -> { if (acquired.equals(JsonValue.TRUE)) { return Actors.tailCall(this, "eat", this.servingsEaten); @@ -128,8 +128,8 @@ public Uni getSecondFork(JsonNumber attempt) { @Remote public Uni eat(JsonNumber serving) { if (VERBOSE) System.out.println(this.getId() + " ate serving number " + serving); - return Actors.call(Actors.ref("Fork", this.secondFork.getString()), "putDown", Json.createValue(this.getId())) - .chain(() -> Actors.call(Actors.ref("Fork", this.firstFork.getString()), "putDown", Json.createValue(this.getId()))) + return Actors.call(this, Actors.ref("Fork", this.secondFork.getString()), "putDown", Json.createValue(this.getId())) + .chain(() -> Actors.call(this, Actors.ref("Fork", this.firstFork.getString()), "putDown", Json.createValue(this.getId()))) .chain(() -> { this.servingsEaten = Json.createValue(serving.intValue() + 1); return Actors.State.set(this, "servingsEaten", this.servingsEaten); diff --git a/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java b/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java index 355ba85a..50896b4d 100644 --- a/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java +++ b/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Cafe.java @@ -32,7 +32,7 @@ public class Cafe extends ActorSkeleton { @Remote public JsonValue occupancy(JsonString table) { - return Actors.call(Actors.ref("Table", table.getString()), "occupancy"); + return Actors.call(this, Actors.ref("Table", table.getString()), "occupancy"); } @Remote @@ -48,7 +48,7 @@ public JsonString seatTable(JsonNumber n, JsonNumber servings) { @Remote public JsonString seatTable(JsonNumber n, JsonNumber servings, JsonString requestId) { - Actors.call(Actors.ref("Table", requestId.getString()), "prepare", Json.createValue(this.getId()), n, servings); + Actors.call(this, Actors.ref("Table", requestId.getString()), "prepare", Json.createValue(this.getId()), n, servings); return requestId; } } diff --git a/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java b/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java index 57efce35..fcffdf91 100644 --- a/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java +++ b/examples/actors-dp-java/src/main/java/com/ibm/research/kar/example/philosophers/Philosopher.java @@ -93,7 +93,7 @@ public TailCall joinTable(JsonString table, JsonString firstFork, JsonString sec @Remote public TailCall getFirstFork(JsonNumber attempt) { - if (Actors.call(Actors.ref("Fork", this.firstFork.getString()), "pickUp", Json.createValue(this.getId())).equals(JsonValue.TRUE)) { + if (Actors.call(this, Actors.ref("Fork", this.firstFork.getString()), "pickUp", Json.createValue(this.getId())).equals(JsonValue.TRUE)) { return new TailCall(this, "getSecondFork", Json.createValue(1)); } else { if (attempt.intValue() > 5) { @@ -106,7 +106,7 @@ public TailCall getFirstFork(JsonNumber attempt) { @Remote public TailCall getSecondFork(JsonNumber attempt) { - if (Actors.call(Actors.ref("Fork", this.secondFork.getString()), "pickUp", Json.createValue(this.getId())).equals(JsonValue.TRUE)) { + if (Actors.call(this, Actors.ref("Fork", this.secondFork.getString()), "pickUp", Json.createValue(this.getId())).equals(JsonValue.TRUE)) { return new TailCall(this, "eat", this.servingsEaten); } else { if (attempt.intValue() > 5) { @@ -120,8 +120,8 @@ public TailCall getSecondFork(JsonNumber attempt) { @Remote public TailCall eat(JsonNumber serving) { if (VERBOSE) System.out.println(this.getId()+" ate serving number "+serving); - Actors.call(Actors.ref("Fork", this.secondFork.getString()), "putDown", Json.createValue(this.getId())); - Actors.call(Actors.ref("Fork", this.firstFork.getString()), "putDown", Json.createValue(this.getId())); + Actors.call(this, Actors.ref("Fork", this.secondFork.getString()), "putDown", Json.createValue(this.getId())); + Actors.call(this, Actors.ref("Fork", this.firstFork.getString()), "putDown", Json.createValue(this.getId())); this.servingsEaten = Json.createValue(serving.intValue() + 1); Actors.State.set(this, "servingsEaten", this.servingsEaten); if (serving.intValue() < this.targetServings.intValue()) { diff --git a/examples/actors-dp-js/philosophers.js b/examples/actors-dp-js/philosophers.js index 70b094b2..f4321023 100644 --- a/examples/actors-dp-js/philosophers.js +++ b/examples/actors-dp-js/philosophers.js @@ -67,7 +67,7 @@ class Philosopher { } async getFirstFork (attempt) { - if (await actor.call(actor.proxy('Fork', this.firstFork), 'pickUp', this.kar.id)) { + if (await actor.call(this, actor.proxy('Fork', this.firstFork), 'pickUp', this.kar.id)) { return actor.tailCall(this, 'getSecondFork', 1) } else { if (attempt > 5) { @@ -79,7 +79,7 @@ class Philosopher { } async getSecondFork (attempt) { - if (await actor.call(actor.proxy('Fork', this.secondFork), 'pickUp', this.kar.id)) { + if (await actor.call(this, actor.proxy('Fork', this.secondFork), 'pickUp', this.kar.id)) { return actor.tailCall(this, 'eat', this.servingsEaten) } else { if (attempt > 5) { @@ -92,8 +92,8 @@ class Philosopher { async eat (serving) { if (verbose) console.log(`${this.kar.id} ate serving number ${serving}`) - await actor.call(actor.proxy('Fork', this.secondFork), 'putDown', this.kar.id) - await actor.call(actor.proxy('Fork', this.firstFork), 'putDown', this.kar.id) + await actor.call(this, actor.proxy('Fork', this.secondFork), 'putDown', this.kar.id) + await actor.call(this, actor.proxy('Fork', this.firstFork), 'putDown', this.kar.id) this.servingsEaten = serving + 1 await actor.state.set(this, 'servingsEaten', this.servingsEaten) if (this.servingsEaten < this.targetServings) { @@ -163,11 +163,11 @@ class Table { class Cafe { async occupancy (table) { - return actor.call(actor.proxy('Table', table), 'occupancy') + return actor.call(this, actor.proxy('Table', table), 'occupancy') } async seatTable (n = 5, servings = 20, requestId = uuidv4()) { - await actor.call(actor.proxy('Table', requestId), 'prepare', this.kar.id, n, servings, requestId) + await actor.call(this, actor.proxy('Table', requestId), 'prepare', this.kar.id, n, servings, requestId) return requestId } } diff --git a/examples/actors-ykt/ykt.js b/examples/actors-ykt/ykt.js index c30c51a3..1c0de397 100644 --- a/examples/actors-ykt/ykt.js +++ b/examples/actors-ykt/ykt.js @@ -138,7 +138,7 @@ class Site { this.company = state.company await actor.state.set(this, 'cleanShutdown', false) if (this.company !== undefined && state.cleanShutdown !== true) { - const employees = await actor.call(actor.proxy('Company', this.company), 'activeEmployees', this.name) + const employees = await actor.call(this, actor.proxy('Company', this.company), 'activeEmployees', this.name) for (const sn of employees) { this.workers[sn] = States.ONBOARDING // Not accurate, but will be corrected on next workerUpdate } diff --git a/examples/misc/actor-timeout-java/src/main/java/com/ibm/research/kar/example/timeout/Test.java b/examples/misc/actor-timeout-java/src/main/java/com/ibm/research/kar/example/timeout/Test.java index 766c832b..159f9fdd 100644 --- a/examples/misc/actor-timeout-java/src/main/java/com/ibm/research/kar/example/timeout/Test.java +++ b/examples/misc/actor-timeout-java/src/main/java/com/ibm/research/kar/example/timeout/Test.java @@ -17,6 +17,7 @@ package com.ibm.research.kar.example.timeout; import static com.ibm.research.kar.Kar.Actors.call; +import static com.ibm.research.kar.Kar.Actors.rootCall; import static com.ibm.research.kar.Kar.Actors.tell; import java.time.Duration; @@ -49,7 +50,7 @@ public class Test extends ActorSkeleton { @Remote public void B() { System.out.println("Entering method B"); - call(this, "A"); // synchronous call to self in a new session -> deadlock + rootCall(this, "A"); // synchronous call to self in a new session -> deadlock System.out.println("Exiting method B"); } @@ -59,7 +60,7 @@ public class Test extends ActorSkeleton { @Remote public void externalA(JsonString target) { ActorRef other = ref("Test", target.getString()); - call(other, "A"); + rootCall(other, "A"); } @Remote public void echo(JsonString msg, JsonNumber count) { diff --git a/examples/misc/service-timeout-java/server-middle/src/main/java/com/ibm/research/kar/example/timeout/middle/MiddleServices.java b/examples/misc/service-timeout-java/server-middle/src/main/java/com/ibm/research/kar/example/timeout/middle/MiddleServices.java index acd0eecf..1be14f15 100644 --- a/examples/misc/service-timeout-java/server-middle/src/main/java/com/ibm/research/kar/example/timeout/middle/MiddleServices.java +++ b/examples/misc/service-timeout-java/server-middle/src/main/java/com/ibm/research/kar/example/timeout/middle/MiddleServices.java @@ -62,7 +62,7 @@ public JsonValue doubler(JsonValue body) { System.out.println("Awake; invoking first actor"); ActorRef myBackend = Kar.Actors.ref("SlowAdder", "Singleton"); - JsonValue firstPart = Kar.Actors.call(myBackend, "add", Json.createValue(data), Json.createValue(delay)); + JsonValue firstPart = Kar.Actors.rootCall(myBackend, "add", Json.createValue(data), Json.createValue(delay)); System.out.println("Received response " + firstPart + "; now sleeping for " + delay + " seconds."); try { @@ -72,7 +72,7 @@ public JsonValue doubler(JsonValue body) { } System.out.println("Awake; invoking second actor"); - JsonValue secondPart = Kar.Actors.call(myBackend, "add", Json.createValue(data), Json.createValue(delay)); + JsonValue secondPart = Kar.Actors.rootCall(myBackend, "add", Json.createValue(data), Json.createValue(delay)); System.out.println("Received response " + secondPart + "; now sleeping for " + delay + " seconds."); try { Thread.sleep(delay * 1000); diff --git a/sdk-java/kar-runtime-liberty/src/main/java/com/ibm/research/kar/Kar.java b/sdk-java/kar-runtime-liberty/src/main/java/com/ibm/research/kar/Kar.java index af1c93b5..26d21bf9 100644 --- a/sdk-java/kar-runtime-liberty/src/main/java/com/ibm/research/kar/Kar.java +++ b/sdk-java/kar-runtime-liberty/src/main/java/com/ibm/research/kar/Kar.java @@ -521,7 +521,7 @@ public static void tell(ActorRef actor, String path, JsonValue... args) { /** * Synchronous actor invocation where the invoked method will execute as part of - * the current session. + * the current chain of calls. * * @param caller The calling actor. * @param actor The target actor. @@ -532,8 +532,7 @@ public static void tell(ActorRef actor, String path, JsonValue... args) { public static JsonValue call(ActorInstance caller, ActorRef actor, String path, JsonValue... args) throws ActorMethodNotFoundException, ActorMethodInvocationException { try { - Response response = sidecar.actorCall(actor.getType(), actor.getId(), path, caller.getSession(), - packArgs(args)); + Response response = sidecar.actorCall(actor.getType(), actor.getId(), path, caller.getSession(), packArgs(args)); return callProcessResponse(response); } catch (WebApplicationException e) { if (e.getResponse() != null && e.getResponse().getStatus() == 404) { @@ -550,44 +549,17 @@ public static JsonValue call(ActorInstance caller, ActorRef actor, String path, } /** - * Synchronous actor invocation where the invoked method will execute as part of - * the specified session. - * - * @param session The session in which to execute the actor method - * @param actor The target actor. - * @param path The actor method to invoke. - * @param args The arguments with which to invoke the actor method. - * @return The result of the invoked actor method. - */ - public static JsonValue call(String session, ActorRef actor, String path, JsonValue... args) - throws ActorMethodNotFoundException, ActorMethodInvocationException, ActorMethodTimeoutException { - try { - Response response = sidecar.actorCall(actor.getType(), actor.getId(), path, session, packArgs(args)); - return callProcessResponse(response); - } catch (WebApplicationException e) { - if (e.getResponse() != null && e.getResponse().getStatus() == 404) { - String msg = responseToString(e.getResponse()); - throw new ActorMethodNotFoundException( - msg != null ? msg : "Not found: " + actor.getType() + "[" + actor.getId() + "]." + path, e); - } else if (e.getResponse() != null && e.getResponse().getStatus() == 408) { - throw new ActorMethodTimeoutException( - "Method timeout: " + actor.getType() + "[" + actor.getId() + "]." + path); - } else { - throw e; - } - } - } - - /** - * Synchronous actor invocation where the invoked method will execute in a new - * session. + * Synchronous actor invocation where the execution of the invoked callee method + * will create a new chain of calls. Typically this method should only be used + * when the caller is not an actor as it does not create a caller-callee dependency + * that can be managed by KAR during failure recovery. * * @param actor The target Actor. * @param path The actor method to invoke. * @param args The arguments with which to invoke the actor method. * @return The result of the invoked actor method. */ - public static JsonValue call(ActorRef actor, String path, JsonValue... args) + public static JsonValue rootCall(ActorRef actor, String path, JsonValue... args) throws ActorMethodNotFoundException, ActorMethodInvocationException { try { Response response = sidecar.actorCall(actor.getType(), actor.getId(), path, null, packArgs(args)); @@ -608,7 +580,10 @@ public static JsonValue call(ActorRef actor, String path, JsonValue... args) /** * Asynchronous actor invocation with eventual access to the result of the - * invocation. + * invocation which will execute in a new top-level chain of calls. + * Typically this method should only be used + * when the caller is not an actor because this API does not create a + * caller-callee dependency that can be managed by KAR during failure recovery. * * @param actor The target Actor. * @param path The actor method to invoke. diff --git a/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/Kar.java b/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/Kar.java index 38d77848..ba715f5e 100644 --- a/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/Kar.java +++ b/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/Kar.java @@ -401,7 +401,7 @@ public static Uni tell(ActorRef actor, String path, JsonValue... args) { /** * Synchronous actor invocation where the invoked method will execute as part of - * the current session. + * the current chain of calls. * * @param caller The calling actor. * @param actor The target actor. @@ -415,30 +415,17 @@ public static Uni call(ActorInstance caller, ActorRef actor, String p } /** - * Synchronous actor invocation where the invoked method will execute as part of - * the specified session. - * - * @param session The session in which to execute the actor method - * @param actor The target actor. - * @param path The actor method to invoke. - * @param args The arguments with which to invoke the actor method. - * @return The result of the invoked actor method. - */ - public static Uni call(String session, ActorRef actor, String path, JsonValue... args) { - return sidecar.actorCall(actor.getType(), actor.getId(), path, session, packArgs(args)) - .chain(resp -> callProcessResponse(resp, actor, path)); - } - - /** - * Synchronous actor invocation where the invoked method will execute in a new - * session. + * Synchronous actor invocation where the execution of the invoked callee method + * will create a new chain of calls. Typically this method should only be used + * when the caller is not an actor as it does not create a caller-callee dependency + * that can be managed by KAR during failure recovery. * * @param actor The target Actor. * @param path The actor method to invoke. * @param args The arguments with which to invoke the actor method. * @return The result of the invoked actor method. */ - public static Uni call(ActorRef actor, String path, JsonValue... args) { + public static Uni rootCall(ActorRef actor, String path, JsonValue... args) { return sidecar.actorCall(actor.getType(), actor.getId(), path, packArgs(args)) .chain(resp -> callProcessResponse(resp, actor, path)); } diff --git a/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/quarkus/ActorEndpoints.java b/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/quarkus/ActorEndpoints.java index fd9c1516..b5a2fcc1 100644 --- a/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/quarkus/ActorEndpoints.java +++ b/sdk-java/kar-runtime-quarkus/src/main/java/com/ibm/research/kar/quarkus/ActorEndpoints.java @@ -186,10 +186,12 @@ public Uni invokeActorMethod(String type, String id, String sessionid, actorObj.setSession(sessionid); Object result = actorMethod.invokeWithArguments(actuals); if (result == null || actorMethod.type().returnType().equals(Void.TYPE)) { + actorObj.setSession(priorSession); return Uni.createFrom().item(Response.status(NO_CONTENT).build()); } else if (result instanceof Uni) { return ((Uni)result) .chain(res -> { + actorObj.setSession(priorSession); if (res == null) { return Uni.createFrom().item(Response.status(NO_CONTENT).build()); } else { @@ -198,16 +200,19 @@ public Uni invokeActorMethod(String type, String id, String sessionid, return Uni.createFrom().item(resp); } }) - .onFailure().recoverWithItem(t -> encodeInvocationError(t)); + .onFailure().recoverWithItem(t -> { + actorObj.setSession(priorSession); + return encodeInvocationError(t); + }); } else { + actorObj.setSession(priorSession); JsonObject response = encodeInvocationResult(result); Response resp = Response.ok().type(KAR_ACTOR_JSON).entity(response.toString()).build(); return Uni.createFrom().item(resp); } } catch (Throwable t) { - return Uni.createFrom().item(encodeInvocationError(t)); - } finally { actorObj.setSession(priorSession); + return Uni.createFrom().item(encodeInvocationError(t)); } } diff --git a/sdk-js/index.d.ts b/sdk-js/index.d.ts index 063110b0..89ce43e4 100644 --- a/sdk-js/index.d.ts +++ b/sdk-js/index.d.ts @@ -158,7 +158,7 @@ export namespace actor { * @param path The actor method to invoke. * @param args The arguments with which to invoke the actor method. */ - export function call (callee: Actor, path: string, ...args: any[]): Promise; + export function rootCall (callee: Actor, path: string, ...args: any[]): Promise; /** * Asynchronously remove all user-level and runtime state of an Actor. diff --git a/sdk-js/index.js b/sdk-js/index.js index a74c110b..b7079472 100644 --- a/sdk-js/index.js +++ b/sdk-js/index.js @@ -144,6 +144,22 @@ function actorProxy (type, id) { return { kar: { type, id } } } const actorTell = (actor, path, ...args) => post(`actor/${actor.kar.type}/${actor.kar.id}/call/${path}`, args, { 'Content-Type': 'application/kar+json', Pragma: 'async' }) function actorCall (...args) { + if (typeof args[1] === 'string') { + // call (callee:Actor, path:string, ...args:any[]):Promise; + // TODO: Once we release an SDK with rootCall, then remove this option + const ta = args.shift() + const path = args.shift() + return postActor(`actor/${ta.kar.type}/${ta.kar.id}/call/${path}`, args, { 'Content-Type': 'application/kar+json' }) + } else { + // call (from:Actor, callee:Actor, path:string, ...args:any[]):Promise; + const sa = args.shift() + const ta = args.shift() + const path = args.shift() + return postActor(`actor/${ta.kar.type}/${ta.kar.id}/call/${path}?session=${sa.kar.session}`, args, { 'Content-Type': 'application/kar+json' }) + } +} + +function actorRootCall (...args) { if (typeof args[1] === 'string') { // call (callee:Actor, path:string, ...args:any[]):Promise; const ta = args.shift() @@ -386,6 +402,7 @@ module.exports = { proxy: actorProxy, tell: actorTell, call: actorCall, + rootCall: actorRootCall, asyncCall: actorAsyncCall, remove: actorDelete, tailCall: actorEncodeTailCall,