Skip to content

Instantly share code, notes, and snippets.

@viktorklang
Last active February 13, 2023 12:13
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save viktorklang/2557678 to your computer and use it in GitHub Desktop.
Save viktorklang/2557678 to your computer and use it in GitHub Desktop.
Minimalist Java Actors
/*
Copyright 2012-2021 Viktor Klang
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package java.klang;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Actor { // Visibility is achieved by volatile-piggybacking of reads+writes to "on"
public static interface Fun<T, R> { public R apply(T t); } // Simple Function interface for Java
public static interface Effect extends Fun<Behavior, Behavior> { }; // An Effect returns a Behavior given a Behavior
public static interface Behavior extends Fun<Object, Effect> { }; // A Behavior is a message (Object) which returns the behavior for the next message
public static interface Address { Address tell(Object msg); }; // An Address is somewhere you can send messages
static abstract class AtomicRunnableAddress implements Runnable, Address { protected final AtomicInteger on = new AtomicInteger(); }; // Defining a composite of AtomcInteger, Runnable and Address
public final static Effect Become(final Behavior behavior) { return new Effect() { public Behavior apply(Behavior old) { return behavior; } }; } // Become is an Effect that returns a captured Behavior no matter what the old Behavior is
public final static Effect Stay = new Effect() { public Behavior apply(Behavior old) { return old; } }; // Stay is an Effect that returns the old Behavior when applied.
public final static Effect Die = Become(new Behavior() { public Effect apply(Object msg) { return Stay; } }); // Die is an Effect which replaces the old Behavior with a new one which does nothing, forever.
public static Address create(final Fun<Address, Behavior> initial, final Executor e) {
final Address a = new AtomicRunnableAddress() {
private final ConcurrentLinkedQueue<Object> mb = new ConcurrentLinkedQueue<Object>();
private Behavior behavior = new Behavior() { public Effect apply(Object msg) { return (msg instanceof Address) ? Become(initial.apply((Address)msg)) : Stay; } };
public final Address tell(Object msg) { if (mb.offer(msg)) async(); return this; }
public final void run() { if(on.get() == 1) { try { final Object m = mb.poll(); if(m != null) behavior = behavior.apply(m).apply(behavior); } finally { on.set(0); async(); } } }
private final void async() { if(!mb.isEmpty() && on.getAndSet(1) == 0) try { e.execute(this); } catch(RuntimeException re) { on.set(0); throw re; } }
};
return a.tell(a); // Make self-aware
}
}
@mictadlo
Copy link

Could you please provide some examples how to use it? Did it someone run successfully on Android?

@viktorklang
Copy link
Author

import static java.klang.Actor.*;
final Address myActor = create( new Fun<Address, Behavior>() {
  public Behavior apply(final Address address) {
    return new Behavior() {
        public Effect apply(final Object message) {
           if (message == "someMessage") {
               doFoo();
           }
           return Stay;
        }
    };
  }
}, yourExecutor);

myActor.tell("someMessage");

Something like that.

@viktorklang
Copy link
Author

Also, why wouldn't it work on Android?

@emolitor
Copy link

Works fine on Android but how about a license?

@viktorklang
Copy link
Author

License added

@plokhotnyuk
Copy link

On Scala version of this actor, I got NPE at checkNotNull call of mbox queue during ping-pong test: https://travis-ci.org/plokhotnyuk/actors/jobs/45208220#L2164
I think source of problem is that processing of last message can occurs between isEmpty and compareAndSet call.
Please see my solution that proposed here: plokhotnyuk/actors@ca46ad6

@plokhotnyuk
Copy link

I have improved the Minimalist Scala Actor for better performance and smaller footprint in memory: https://github.com/plokhotnyuk/actors/blob/42f39f15edc84ce5b6960e6bf7f1b0e75563e5fc/src/test/scala/com/github/gist/viktorklang/Actor.scala
@viktorklang , please review it if it's worth a look.

@butjeffsays
Copy link

@viktorklang I'm late to the party, but I love using this class as an alternative when I don't want the heavyweight akka/scala frameworks as dependencies.
@plokhotnyuk I encountered the same NPE, simply checking the mailbox's return from poll() as you did alleviated the issue. Took an embarrassing amount of time to discover and now i'm even more embarrassed to find that your solution was posted years ago...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment