Closed roegerle closed 2 years ago
@roegerle is it possible for you to provide a test case to reproduce this?
@roegerle is it possible for you to provide a test case to reproduce this?
@jakzal The Map
is a plain HashMap
but should be ConcurrentHashMap
. My guess is that he's running both Scooter blocking mailbox and non-blocking mailboxes at the same time.
@roegerle In case you are doing otherwise, the TestWorld
is meant only for testing. As @jakzal requested, please do provide a test that reproduces the issue.
https://github.com/roegerle/vlingo-issue-96
Run it a few times to get the error. Last time it took four runs.
https://github.com/roegerle/vlingo-issue-96
Run it a few times to get the error. Last time it took four runs.
Thanks. I have changed the HashMap
to ConcurrentHashMap
, but that will ultimately not fix your issue. There are a few problems using TestWorld
and TestActor
the way that you are using them:
The TestWorld and TestActor are meant to be used in simple tests for checking the messaging delivery and behavior as well as internal state transitions. This is stated in the documentation:
All actors created directly through the
TestWorld
are assigned a mailbox type ofTestMailbox
. TheTestMailbox
does not queue messages, but performs immediate, synchronous delivery. This is done with the purpose of instantly testing the impact of each received message on the receiving actor. Such a test does not have to wait for asynchronous message delivery, making it much simpler to see the resulting message-drive state transition on the receiving actor, for example. When testing actors created through the conventional World, the test must employ a mechanism such asAccessSafely
to eventually see and assert against the expected outcomes. It is not always convenient to mock a protocol interface that is backed by aAccessSafely
instance, making an actor created through theTestWorld
and possessing aTestMailbox
an essential tool. Yet, you must be careful when using an actor created by theTestWorld
because such actors don't have asynchronous semantics. If you use several such actors together, it is almost certain that you will experience race conditions. As with all software tools,TestWorld
and actors created through it, come with tradeoffs. Use the tools with this knowledge. Note that inside everyTestWorld
is a normal World that you can query.
An Actor
, even a TestActor
must not be entered by multiple threads simultaneously. All Actor
messages must be entered by a single thread only. Your Stream
runs in parallel. Actor
s are strictly single threaded but multiple individual Actor
s can be run in parallel.
This could be overcome by introducing synchronized in the TestMailbox
. Yet, ultimately it is still possible to introduce parallel processing by passing a non-blocking Actor
into an Actor
that is wrapped by TestActor
.
Since Stream runs in parallel it will very likely complete before even synchronized actor message delivery, which will cause the World to terminate prematurely. This pulls the operating environment out from under the Actor
under test. As you can read in the documentation, that's one reason why we have a synchronizing tool known as AccessSafely
.
Basically we can't support the use of TestWorld
and TestActor
in ways that are unintended. Please use as intended.
You will find a new test that reimplements yours, but has been renamed. You can see that it fails for various reasons. My latest test sees this (which is the TestWorld
terminating too soon):
[ERROR] Errors:
[ERROR] TestkitTest.testTestWorldActorUsedInParallelFails:102->lambda$testTestWorldActorUsedInParallelFails$0:104 NullPointer
Thus I have marked this test ignored. You can enable it to see the various and predictable failure points.
All above changes are available in the latest vlingo/xoom-actors:1.9.4-SNAPSHOT
.
@roegerle @jakzal BTW, I did not mean that an actor must be run on a single unique thread. Any actor can be called by any number of unique threads, but by only one at a time.
I thank you for the thourough response but I'm a little confused. I understand the actors themselves are not thread safe but is accessing them via the generated proxy thread safe? Which is what I thought I was doing.
The Mailbox
type and the delivery semantics are what make actors thread safe. The proxy is used to reify method invocations on the proxy into objects of type Message
and, under normal mailbox use, enqueue that message in the actor's mailbox.
However, there are two broad mailbox types under discussion in this context:
TestWorld
. When a new message is buffered the message is scheduled for delivery, and which occurs when a thread is available.
ConcurrentQueueMailbox
schedules using a thread pool. This is the typical mailbox type.TestMailbox
does. It's considered blocking because the direct call-through uses the sender's thread, and thus blocks the sender until the actor has handled that message.When you are using the TestWorld
and a TestActor
wrapper, the underlying mailbox type is the blocking TestMailbox
. The purpose is to remove asynchronous delivery because the matter at hand is to test the protocol-to-state-transition correctness.
I hope that helps.
It does. Thank you.
version 1.9.3 java.util.ConcurrentModificationException at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1221) at io.vlingo.xoom.actors.testkit.TestWorld.track(TestWorld.java:131) at io.vlingo.xoom.actors.plugin.mailbox.testkit.TestMailbox.send(TestMailbox.java:75)
I'm working under the assumption that TestWorld and World for that matter are thread safe.