Skip to content

Actor state save best practices #854

Open
@manuelserradev

Description

@manuelserradev

Expected Behavior

Saving actor state does not fail.

Actual Behavior

Saving an actor state fails with the following:

java.util.ConcurrentModificationException: null
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1630)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1628)
	at io.dapr.actors.runtime.ActorStateManager.flush(ActorStateManager.java:286)
	at io.dapr.actors.runtime.ActorStateManager.lambda$save$15(ActorStateManager.java:272)
	at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73)
	at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:32)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:228)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
	at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onComplete(FluxHide.java:147)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1840)
	at reactor.core.publisher.MonoCallable.subscribe(MonoCallable.java:62)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
	at reactor.core.publisher.MonoIgnorePublisher.subscribe(MonoIgnorePublisher.java:57)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
	at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:203)
	at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4475)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
	at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
	at reactor.core.publisher.Mono.block(Mono.java:1741)	

Steps to Reproduce the Problem

Implement an actor with two methods like the following:

  @Override
  public void enqueue(String taskId) {

    var mockPayload = "{}";

    this.getActorStateManager().set("payload_%s".formatted(taskId), mockPayload).block();

    System.out.println("Saved payload %s".formatted(taskId));

    this.saveState().block();
  }

  @Override
  public void ack(String taskId) {

    var mockPayload = this.getActorStateManager().get("payload_%s".formatted(taskId), String.class).block();

    System.out.println("Retrieved payload %s".formatted(taskId));
    System.out.println("Payload was %s".formatted(mockPayload));

    this.getActorStateManager().remove("payload_%s".formatted(taskId)).block();

    this.saveState().block();
  }

And play around, try to call:

  1. enqueue(1)
  2. enqueue(2)
  3. enqueue(3)
  4. ack(1)

Should fail.

Controller for the sake of completeness:

@RestController
@RequiredArgsConstructor
public class SerializerActorController {

  private final ActorProxyBuilder<SerializerActor> actorProxyBuilder;

  @GetMapping(value = "/enqueue/{taskId}")
  public ResponseEntity<Void> enqueue(@PathVariable("taskId") String taskId) {

    try {
      var actorId = new ActorId("actorId");
      var actor = actorProxyBuilder.build(actorId);

      actor.enqueue(taskId);

      return ResponseEntity.ok().build();
    } catch (Exception ex) {
      ex.printStackTrace();
      return ResponseEntity.internalServerError().build();
    }
  }

  @GetMapping(value = "/ack/{taskId}")
  public ResponseEntity<Void> ack(@PathVariable("taskId") String taskId) {

    try {
      var actorId = new ActorId("actorId");
      var actor = actorProxyBuilder.build(actorId);

      actor.ack(taskId);

      return ResponseEntity.ok().build();
    } catch (Exception ex) {
      ex.printStackTrace();
      return ResponseEntity.internalServerError().build();
    }
  }
}

Release Note

RELEASE NOTE:

FIX Solved concurrency on actor state saving stage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1kind/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions