Slide 1

Slide 1 text

Write You an Actor (System) for Great Good! (with JBang, JDK 19, records, pattern matching and virtual threads!)

Slide 2

Slide 2 text

Andrea Peruffo @and_prf Edoardo Vacchi @evacchi

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

This Is Not A Talk About Actor Systems

Slide 5

Slide 5 text

This Is Not A Talk About Actor Systems

Slide 6

Slide 6 text

This Is Not Only A Talk About Actor Systems

Slide 7

Slide 7 text

evacchi.github.io

Slide 8

Slide 8 text

@evacchi About Me Edoardo Vacchi @evacchi ● PL/DSL Research @ UniMi ● R&D @ UniCredit Bank ● Drools, Kogito @ Red Hat

Slide 9

Slide 9 text

gist.github.com/viktorklang/2362563

Slide 10

Slide 10 text

gist.github.com/viktorklang/2362563

Slide 11

Slide 11 text

Has Java reached feature parity with 10-years ago Scala?

Slide 12

Slide 12 text

Has Java reached feature parity with 10-years ago Scala?

Slide 13

Slide 13 text

Has Java reached feature parity with 10-years ago Scala?

Slide 14

Slide 14 text

Has Java reached feature parity with 10-years ago Scala?

Slide 15

Slide 15 text

package io.github.evacchi; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import static java.lang.System.out; public interface Actor { interface Behavior extends Function {} interface Effect extends Function {} interface Address { Address tell(Object msg); } static Effect Become(Behavior like) { return old -> like; } static Effect Stay = old -> old; static Effect Die = Become(msg -> { out.println("Dropping msg [" + msg + "] due to severe case of death."); return Stay; }); record System(ExecutorService executorService) { public Address actorOf(Function
initial) { abstract class AtomicRunnableAddress implements Address, Runnable { final AtomicInteger on = new AtomicInteger(0); } var addr = new AtomicRunnableAddress() { final ConcurrentLinkedQueue mb = new ConcurrentLinkedQueue<>(); Behavior behavior = m -> (m instanceof Address self) ? Become(initial.apply(self)) : Stay; public Address tell(Object msg) { mb.offer(msg); async(); return this; } public void run() { try { if (on.get() == 1) { var m = mb.poll(); if (m!=null) { behavior = behavior.apply(m).apply(behavior); } }} finally { on.set(0); async(); }} void async() { if (!mb.isEmpty() && on.compareAndSet(0, 1)) { try { executorService.execute(this); } catch (Throwable t) { on.set(0); throw t; }}} }; return addr.tell(addr); } } } evacchi.github.io

Slide 16

Slide 16 text

@evacchi What You Will Bring Home ● Learn new Java 19 features ● Learn an interesting concurrency model ● Learn how to run code snippets with JBang

Slide 17

Slide 17 text

@evacchi What You Will Bring Home ● Learn new Java 19 features ● Learn an interesting concurrency model ● Learn how to run code snippets with JBang ● Learn how to write concise Java program with tricks that will make your coworkers mad

Slide 18

Slide 18 text

Agenda

Slide 19

Slide 19 text

@evacchi Agenda ● What is an actor? ● Comparison between actor runtimes ● Features of Java we will be using ☕ ● Examples of actors (live coding) ● An actor runtime (live coding) ● A typed actor runtime (live coding) ☕ ● An actor-based chat (live coding) ● Virtual Threads and Project Loom ● A Loom-based chat (live coding) Live coding with

Slide 20

Slide 20 text

Actor Definition

Slide 21

Slide 21 text

@evacchi Actor Definition An actor is a computational entity that, in response to a message it receives, can concurrently: ● send a finite number of messages to other actors; ● create a finite number of new actors; ● designate the behavior to be used for the next message it receives. "Everything" is an actor.

Slide 22

Slide 22 text

@evacchi Actor Model ● Modelling stateful systems ● Distributed systems (e.g. leveraging location transparency) ● IoT (e.g. Digital twins)

Slide 23

Slide 23 text

@evacchi Actor Model ● Concurrency Model ● Simple ● "Single-Thread" reasoning ● Message-based, "Mailbox"

Slide 24

Slide 24 text

@evacchi Implementation Function + State + Message Queue

Slide 25

Slide 25 text

@evacchi Implementation Function + State + Message Queue

Slide 26

Slide 26 text

@evacchi Implementation Function + State + Message Queue

Slide 27

Slide 27 text

@evacchi 🖂 ➡ 🖂 🖂 🖂 🖂 🖂 🖂 Implementation Private State Behavior mailbox

Slide 28

Slide 28 text

@evacchi Implementation 🖂 🖂 🖂 🖂 🖂 🖂 🖂 Private State Behavior

Slide 29

Slide 29 text

@evacchi Implementation 🖂 🖂 🖂 🖂 🖂 🖂 Private State Behavior 🖂

Slide 30

Slide 30 text

@evacchi Implementation 🖂 🖂 🖂 🖂 🖂 🖂 Private State Behavior 🖂

Slide 31

Slide 31 text

@evacchi Implementation 🖂 🖂 🖂 🖂 🖂 🖂 Private State Behavior 🖂 📤

Slide 32

Slide 32 text

@evacchi Implementation 🖂 🖂 🖂 🖂 🖂 🖂 Private State Behavior 🖂 🖂 ➡

Slide 33

Slide 33 text

@evacchi Messages vs Methods ● you send a message to an actor ● you invoke a method on an object ● however in Smalltalk you send "messages" to objects

Slide 34

Slide 34 text

@evacchi Messages vs Methods ● you send a message to an actor ● you invoke a method on an object ● however in Smalltalk you send "messages" to objects ● in fact, some people think that actors are "proper" OOP as envisioned by Alan Kay

Slide 35

Slide 35 text

https://twitter.com/joeerl/status/1107348549132533760 @evacchi

Slide 36

Slide 36 text

https://twitter.com/joeerl/status/1107544833911128064 @evacchi

Slide 37

Slide 37 text

https://twitter.com/joeerl/status/1107544833911128064

Slide 38

Slide 38 text

@evacchi Spawning Actors

Slide 39

Slide 39 text

@evacchi Vending Machines (FSM)

Slide 40

Slide 40 text

Actor Runtimes

Slide 41

Slide 41 text

@evacchi When Scala Roamed The World ● Java 8 in 2014 ● Context: JDK 5/6/7 ○ Mainstream statically typed OOP programming languages were verbose (C++/Java/C#) ○ Dynamic languages were more concise (Python/Ruby/Groovy) ● Enter Scala ○ Static typing ○ Dynamic feel / conciseness ○ Actor library

Slide 42

Slide 42 text

@evacchi When Scala Roamed The World ● Context: JDK 5/6/7 ○ Mainstream statically typed OOP programming languages were verbose (C++/Java/C#) ○ Dynamic languages were more concise Python/Ruby/Groovy ● Enter Scala ○ Static typing ○ Dynamic feel / conciseness ○ Actor library

Slide 43

Slide 43 text

@evacchi Akka (Scala) case object Ping class Pingponger extends Actor { var count = 10 def receive: Any => Unit = { case Ping => println(s"${self.path} received ping, count down $count") if (this.count > 0) { this.count = this.count - 1 sender() ! Ping } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping, ponger)

Slide 44

Slide 44 text

@evacchi Akka (Scala) case class Ping(count: Int) class Pingponger extends Actor { def receive: Any => Unit = { case Ping(count) => println(s"${self.path} received ping, count down $count") if (count > 0) { sender() ! Ping(count - 1) } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping(10), ponger)

Slide 45

Slide 45 text

@evacchi Akka (Scala) case class Ping(count: Int) class Pingponger extends Actor { def receive(msg: Any): Unit = msg match { case Ping(count) => println(s"${self.path} received ping, count down $count") if (count > 0) { sender() ! Ping(count - 1) } } } val system = ActorSystem("pingpong") val pinger = system.actorOf(Props[Pingponger](), "pinger") val ponger = system.actorOf(Props[Pingponger](), "ponger") pinger.tell(Ping(10), ponger)

Slide 46

Slide 46 text

@evacchi Akka + Java public class MyUntypedActor extends UntypedActor { LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { if (message instanceof Ping) { Ping p = (Ping) message; if (p.count() > 0) { log.info("Received ping : " + p.count()); getSender().tell(new Ping(p.count() - 1), getSelf()); } } }

Slide 47

Slide 47 text

@evacchi Scala Groovy Ruby Python Java

Slide 48

Slide 48 text

@evacchi Erlang pingponger(Name, Main) -> receive {ping, Count, Ping_PID} -> io:format("~s received ping, count down ~w~n", [Name, Count]), if Count > 0 -> Ping_PID ! {ping, Count - 1, self()}, pingponger(Name, Main); true -> exit ("done") end end. main(_) -> Ping1_PID = spawn(fun() -> pingponger("pinger", self()) end), Ping2_PID = spawn(fun() -> pingponger("ponger", self()) end), Ping1_PID ! {ping, 10, Ping2_PID}.

Slide 49

Slide 49 text

@evacchi Elixir defmodule Main do def pingponger(name) do receive do {:ping, count, sender} -> IO.puts "#{name} received ping, count down #{count}" if count > 0 do send sender, {:ping, count - 1, self()} pingponger(name) else :ok end end end end pid1 = spawn fn -> Main.pingponger("pinger") end pid2 = spawn fn -> Main.pingponger("ponger") end send pid1, {:ping, 10, pid2}

Slide 50

Slide 50 text

@evacchi Scala Groovy Ruby Python Java Erlang Elixir

Slide 51

Slide 51 text

@evacchi Akka + Java public class MyUntypedActor extends UntypedActor { LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { if (message instanceof Ping) { Ping p = (Ping) message; if (p.count() > 0) { log.info("Received ping : " + p.count()); getSender().tell(new Ping(p.count() - 1), getSelf()); } } } Can we improve over this?

Slide 52

Slide 52 text

Tools Of The Trade

Slide 53

Slide 53 text

@evacchi (Ab)using Language Defaults To Reduce Noise public class Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }

Slide 54

Slide 54 text

@evacchi (Ab)using Language Defaults To Reduce Noise public class Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }

Slide 55

Slide 55 text

@evacchi (Ab)using Language Defaults To Reduce Noise public interface Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }

Slide 56

Slide 56 text

@evacchi (Ab)using Language Defaults To Reduce Noise public interface Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }

Slide 57

Slide 57 text

@evacchi (Ab)using Language Defaults To Reduce Noise import static java.lang.System.*; public interface Main { public static final String HELLO = "hello world"; public static void main(String[] args) { System.out.println(HELLO); } }

Slide 58

Slide 58 text

@evacchi (Ab)using Language Defaults To Reduce Noise import static java.lang.System.*; interface Main { String HELLO = "hello world"; static void main(String... args) { out.println(HELLO); } }

Slide 59

Slide 59 text

https://openjdk.org/projects/amber/design-notes/on-ramp

Slide 60

Slide 60 text

public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } @evacchi

Slide 61

Slide 61 text

public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } @evacchi

Slide 62

Slide 62 text

public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } void main() { println("Hello World"); } @evacchi

Slide 63

Slide 63 text

Is This Code-Golfing? me recent Java syntax

Slide 64

Slide 64 text

@evacchi More “Code Golfing” public class Auth { private final String login; private final String token; public Auth(String login, String token) { this.login = login; this.token = token; } public String login() { return this.login; } public String token() { return this.token; } public boolean equals(Object o) { ... } public int hashCode() { return Objects.hash(login, token); } public String toString() { ... } } public class Anon { private final String email; public Anon(String email) { this.email = email; } public String email() { return this.email; } public boolean equals(Object o) { ... } public int hashCode() { ... } public String toString() { ... } }

Slide 65

Slide 65 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { if (message instanceof Auth) { Auth a = (Auth) message; check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon) { Anon a = (Anon) message; // grant guest access } else unhandled(message); }

Slide 66

Slide 66 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { if (message instanceof Auth) { Auth a = (Auth) message; check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon) { Anon a = (Anon) message; // grant guest access } else unhandled(message); }

Slide 67

Slide 67 text

@evacchi Enhanced instanceof public void onReceive(Object message) throws Exception { if (message instanceof Auth a) { check(a.login()); if (validate(a.login(), a.token())) { // grant full access } else { // error } } else if (message instanceof Anon a) { // grant guest access } else unhandled(message); } pattern matching

Slide 68

Slide 68 text

@evacchi More “Code Golfing” public class Auth { private final String login; private final String token; public Auth(String login, String token) { this.login = login; this.token = token; } public String login() { return this.login; } public String token() { return this.token; } public boolean equals(Object o) { ... } public int hashCode() { return Objects.hash(login, token); } public String toString() { ... } } public class Anon { private final String email; public Anon(String email) { this.email = email; } public String email() { return this.email; } public boolean equals(Object o) { ... } public int hashCode() { ... } public String toString() { ... } }

Slide 69

Slide 69 text

@evacchi Records public record Auth(String login, String token) {} public record Anon(String email) {}

Slide 70

Slide 70 text

@evacchi Enhanced instanceof public void onReceive(Object message) throws Exception { if (message instanceof Auth(var login, var token)) { check(login); if (validate(login, token)) { // grant full access } else { // error } } else if (message instanceof Anon(String email)) { // grant guest access } else unhandled(message); } deconstruction patterns

Slide 71

Slide 71 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } }

Slide 72

Slide 72 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } Improved Switch

Slide 73

Slide 73 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } Records

Slide 74

Slide 74 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } deconstruction patterns

Slide 75

Slide 75 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } } var pattern

Slide 76

Slide 76 text

@evacchi More “Code Golfing” public void onReceive(Object message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; default -> unhandled(message); } }

Slide 77

Slide 77 text

@evacchi Records public record Auth(String login, String token) {} public record Anon(String email) {}

Slide 78

Slide 78 text

@evacchi Sealed Hierarchies sealed interface LoginInfo {} public record Auth(String login, String token) implements LoginInfo {} public record Anon(String email) implements LoginInfo {}

Slide 79

Slide 79 text

@evacchi Exhaustive Pattern Matching public void onReceive(LoginInfo message) throws Exception { switch (message) { case Auth(var login, var token) -> { check(login); if (validate(login, token)) { // grant full access } else { // error } } case Anon(var email) -> { /* guest access */ }; } } Sealed Type Hierarchy No Default Case!

Slide 80

Slide 80 text

@evacchi Scala def onReceive(Any: message): Unit { message match { case Auth(login, token) => { check(login) if (validate(login, token)) { // grant full access } else { // error } } case Anon(email) => { /* guest access */ }; } }

Slide 81

Slide 81 text

@evacchi Akka + Java + Pattern Matching public class MyUntypedActor extends UntypedActor { LoggingAdapter log = Logging.getLogger(getContext().system(), this); public void onReceive(Object message) throws Exception { switch (message) { case String m -> { log.info("Received String message: {}", message); getSender().tell(message, getSelf()); } default -> unhandled(message); } } }

Slide 82

Slide 82 text

@evacchi Akka + Java static class Pingponger extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .match( Ping.class, ping -> { out.println(getSelf().path() + " received ping, count down " + ping.getCount()); if (ping.getCount() > 0) { getSender().tell(new Ping(ping.getCount() - 1), getSelf()); } }) .build(); } } public static void main(String... args) { ActorSystem system = ActorSystem.apply("pingpong"); ActorRef pinger = system.actorOf(Props.create(Pingponger.class), "pinger"); ActorRef ponger = system.actorOf(Props.create(Pingponger.class), "ponger"); pinger.tell(new Ping(10), ponger); }

Slide 83

Slide 83 text

gist.github.com/viktorklang/2557678

Slide 84

Slide 84 text

@evacchi Agenda ● What is an actor? ● Comparison between actor runtimes ● Features of Java we will be using ☕ ● Examples of actors (live coding) ● An actor runtime (live coding) ● A typed actor runtime (live coding) ☕ ● An actor-based chat (live coding) ● Virtual Threads and Project Loom ● A Loom-based chat (live coding) Live coding with

Slide 85

Slide 85 text

Live Coding with

Slide 86

Slide 86 text

@evacchi My First Actor

Slide 87

Slide 87 text

@evacchi Vending Machines (FSM)

Slide 88

Slide 88 text

Closures vs Classes

Slide 89

Slide 89 text

@evacchi Implementation Function + State + Message Queue

Slide 90

Slide 90 text

@evacchi Closures vs Classes The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said “Master, I have heard that objects are a very good thing - is this true?” Qc Na looked pityingly at his student and replied…

Slide 91

Slide 91 text

@evacchi Closures vs Classes “Foolish pupil – objects are merely a poor man’s closures.”

Slide 92

Slide 92 text

@evacchi Closures vs Classes Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

Slide 93

Slide 93 text

@evacchi Closures vs Classes On his next walk with Qc Na, Anton attempted to impress his master by saying “Master, I have diligently studied the matter, and now understand that objects are truly a poor man’s closures.” Qc Na responded by hitting Anton with his stick, saying…

Slide 94

Slide 94 text

@evacchi Closures vs Classes “When will you learn? Closures are a poor man's object.”

Slide 95

Slide 95 text

@evacchi Closures vs Classes At that moment, Anton became enlightened. http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html

Slide 96

Slide 96 text

@evacchi Agenda ● What is an actor? ● Comparison between actor runtimes ● Features of Java we will be using ☕ ● Examples of actors (live coding) ● An actor runtime (live coding) ● A typed actor runtime (live coding) ☕ ● An actor-based chat (live coding) ● Virtual Threads and Project Loom ● A Loom-based chat (live coding) Live coding with

Slide 97

Slide 97 text

Write You a Chat

Slide 98

Slide 98 text

@evacchi Server

Slide 99

Slide 99 text

@evacchi Client Manager

Slide 100

Slide 100 text

@evacchi Client Manager

Slide 101

Slide 101 text

@evacchi Thou Shall Not Block!

Slide 102

Slide 102 text

Loom

Slide 103

Slide 103 text

@evacchi Green Threads ● in the Java 1.0 days, some JVMs "green" (user-mode) threads. ● The green threads of the 90s still had large, monolithic stacks. ● A product of their time ○ systems were single-core ○ OSes did not have thread support at all. https://www.infoq.com/articles/java-virtual-threads/

Slide 104

Slide 104 text

@evacchi Virtual Threads ● Virtual threads: only superficial similarity to Green Threads ● More in common with user-mode threads in Go or Erlang ● However: semantically identical to java.lang.Thread

Slide 105

Slide 105 text

@evacchi Virtual Threads ● Virtual threads: only superficial similarity to Green Threads ● More in common with user-mode threads in Go or Erlang ● However: semantically identical to java.lang.Thread

Slide 106

Slide 106 text

@evacchi Green Threads ● Solaris 2.6 software ● the green threads library was user-level ● the Solaris system could process only one green thread at a time, ● Solaris handled the Java runtime as a many-to-one threading implementation ● Java applications could not interoperate with existing MT applications in the Solaris environment. ● Java threads could not run in parallel on multiprocessors. ● An MT Java application could not harness true OS concurrency for faster applications on either uniprocessors or multiprocessors. Green threads library was replaced with native Solaris threads for Java on the Solaris 2.6 platform; this is carried forward on the Solaris 7 and Solaris 8 platforms. https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqe/

Slide 107

Slide 107 text

@evacchi Loom https://wiki.openjdk.org/display/loom/Main “Project Loom is intended to explore, incubate and deliver Java VM features and APIs built on top of them for the purpose of supporting easy-to-use, high-throughput lightweight concurrency and new programming models on the Java platform.”

Slide 108

Slide 108 text

@evacchi Loom ● Virtual Threads are mounted on platform threads (carrier threads) ● Yield in std library

Slide 109

Slide 109 text

https://www.youtube.com/watch?v=449j7oKQVkc

Slide 110

Slide 110 text

@evacchi Thou Shall Not Block!

Slide 111

Slide 111 text

@evacchi Thou Shall Not Block!

Slide 112

Slide 112 text

Write You a Loom Chat

Slide 113

Slide 113 text

Game of Loom: implementation patterns and performance implications playing with virtual threads by Mario Fusco @mariofusco Learn More This Thursday!

Slide 114

Slide 114 text

@evacchi bit.ly/actor19

Slide 115

Slide 115 text

No content