├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── example └── java │ └── com │ └── zakgof │ └── actr │ ├── example │ ├── ActrForkJoinMergeSort.java │ └── AskTellLaterExample.java │ └── vsakka │ ├── ActrQuickstart.java │ ├── Greeter.java │ └── Printer.java ├── main └── java │ └── com │ └── zakgof │ └── actr │ ├── Actr.java │ ├── IActorBuilder.java │ ├── IActorRef.java │ ├── IActorScheduler.java │ ├── IActorSystem.java │ ├── IForkBuilder.java │ ├── Schedulers.java │ └── impl │ ├── ActorImpl.java │ ├── ActorSystemImpl.java │ ├── BlockingThreadScheduler.java │ ├── ConcurrentDoublyLinkedList.java │ ├── ExecutorBasedScheduler.java │ ├── FastRegSet.java │ ├── IRegSet.java │ ├── MapRegSet.java │ ├── SingleThreadScheduler.java │ └── ThreadPerActorScheduler.java └── test └── java └── com └── zakgof └── actr └── test ├── BasicTest.java ├── NestedAskTest.java └── ShutdownTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | /.settings 4 | /.factorypath 5 | /target 6 | /.gradle 7 | /.idea/ 8 | /build/ 9 | /bin/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: skip 3 | 4 | os: linux 5 | dist: trusty 6 | jdk: openjdk11 7 | 8 | addons: 9 | sonarcloud: 10 | organization: "zakgof" 11 | token: 12 | secure: "UmcKUI+AbMkKHi4gOe3c6TnO6j9fIaHvxIQI/iXfX+P05VYsPqxyzJlgK9XatnY1oMMilSvkoAxw3JWlm8KGbymaMPuaJfhCkPYainR91mOLimblkUzZah1W0nMXpSu8rnoEHDIgGjstFzSveOHRk/p9d4IFMkgIWLRx5ZmwlPEsM9yOSmMpuqDU+Wl5YoKn0JxDCmrdkA+CtlyQpuCMB/oEk0eZWc9WtCJpT305IvgfzjroDoIs4pntjyQ/zf91qcm/fncvKkjErshHLW36OCZWplQsniMKVHpq1dcJgF0Y3i42IYuZMd5yrXuftXsfU/XbOLrdVBmlHPewORBBNLasvagN7Iz5JGv5N+CCoVigU9EhCG1ypyHbyTUWN56V+64HVrRxONRPvGc+Lp9P1X6cGyQWMvRWr7hM7ykigLFQXSFSuNPM3D0CR9x0uf7/dtaxn0OoDYV+eTTGunsxNzr13GUr29ZpI7WsmBcvpnVQT+0ERHlH+VyWZmd0RvZQ5bi1gXjODYqwbJhwn7GknP0ZVMs3AvxdpUOsx+6GujX7IFP2Ttr9howlIPwEzC84vvoPE75d2sUbDHLXry3DnssOTgWJJiN7SfIvjrbMx6cvSNlYsEgwx6GOybEglepVbl69VpGxOMSo9u9441wd3Eyq++65rwf2mZC2aBpI6+0=" 13 | 14 | script: 15 | - ./gradlew test sonarqube -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## actr 2 | ![Travis CI](https://travis-ci.org/zakgof/actr.svg?branch=release) 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.zakgof/actr/badge.svg)](https://mvnrepository.com/artifact/com.github.zakgof/actr) 4 | 5 | Simple actor model implementation for Java 6 | 7 | - Simple API: sending _ask_ and _tell_ messages is just calling class methods 8 | - POJOs as Actors: focus on business logics, no need to extend any frameworks classes 9 | - Type safe: no need for instanceof/cast, compile time check prevents sending wrong message to a wrong actor 10 | - High performance: lock-free implementation and lightweight actor creation 11 | 12 | Actor code is guaranteed to be executed in thread-safe context: 13 | - no concurrent calls for a particular actor (although subsequent calls may be dispatched to different threads) 14 | - actor state can be safely read/written from actor code without any synchronized/volatile specifiers 15 | 16 | Actr's API philosophy is discussed here: https://medium.com/@zakgof/type-safe-actor-model-for-java-7133857a9f72 17 | 18 | #### Schedulers 19 | 20 | Schedulers are available to be configured on per-actor or per-actor-system basis. 21 | 22 | - Shared ForkJoinPool scheduler (the default) 23 | All actors share the common work stealing `ForkJoinPool`. This option is best for CPU-intensive actors. 24 | 25 | - Thread per actor (pinned thread) scheduler 26 | Each actor owns a thread and all calls to the actor execute in that dedicated thread. It is useful, in particular, when wrapping non thread safe API. 27 | **NEW !** JDK's project Loom Virtual Threads are also supported - check this article: https://medium.com/@zakgof/a-simple-benchmark-for-jdk-project-looms-virtual-threads-4f43ef8aeb1 28 | 29 | - Fixed thread pool scheduler 30 | Uses a pool of a predefined number or threads for scheduling actor calls. It might be beneficial compared to ForkJoinPools for actors involving some io when actor's CPU utilization is not maximum. 31 | 32 | - Scheduler based on a user-provided ExecutorService for a more flexible option. 33 | 34 | It's easy to introduce your own fine-tuned scheduler by just implementing `IActorScheduler`. 35 | 36 | #### Comparison to akka 37 | 38 | Akka will require more boilerplate code. Code with Actr will be more concise. 39 | 40 | | | Akka | Actr | 41 | | ------------- | ------------- | ------------- | 42 | | Type safety | No. Sending any message to any actor won't reveal any problems at compile time| Type safe | 43 | | Actor implementation class | Must extend AbstractActor | No constraints | 44 | | Message | Must create a class for every message type | Message type is represented by a method | 45 | | Message type dispatching | Must implement dispatching| Message type = method, no need for implementing dispatching | 46 | | Passing multiple params | Must pack into a single message class | Message type = method, so supported | 47 | 48 | Compare the same example implemented with 49 | [akka](https://github.com/akka/akka-quickstart-java.g8/tree/2.5.x/src/main/g8/src/main/java/com/lightbend/akka/sample) and [actr](https://github.com/zakgof/actr/tree/master/src/example/java/com/zakgof/actr/vsakka). (Note the difference in Printer implementation. With akka, the implementation is 41 lines long with only 1 line of business code (line 34)) 50 | 51 | #### Performance 52 | Actr outperforms Akka on common actor operations. A complete opensource benchmark is available here: https://github.com/zakgof/akka-actr-benchmark 53 | 54 | ### Setup 55 | Actr is on Maven Central 56 | 57 | #### Gradle 58 | ````groovy 59 | implementation 'com.github.zakgof:actr:0.4.2' 60 | ```` 61 | 62 | #### Maven 63 | ````xml 64 | 65 | com.github.zakgof 66 | actr 67 | 0.4.2/version> 68 | 69 | ```` 70 | 71 | ### Usage 72 | 73 | Having a POJO class 74 | ````java 75 | private static class Printer { 76 | 77 | public void print(String s) { 78 | // This is called in Printer's thread 79 | System.err.println("[Printer] " + s); 80 | } 81 | public String getName() { 82 | // This is called in Printer's thread 83 | return "Printer-1"; 84 | } 85 | } 86 | ```` 87 | 88 | Create an actor 89 | 90 | ````java 91 | final IActorRef printerActor = Actr.newActorSystem("default").actorOf(Printer::new); 92 | ```` 93 | 94 | Call Printer from another actor 95 | ````java 96 | 97 | 98 | public void run() { 99 | // Tell: send text to print 100 | printerActor.tell(printer -> printer.print("Hello !")); 101 | 102 | // Ask: retrieve printer name. Printer#getName runs in Printer's thread, #printerNameReply runs in Caller's thread 103 | printerActor.ask(Printer::getName, this::printerNameReceived); 104 | } 105 | 106 | private void printerNameReceived(String printerName) { 107 | // Do smth with Printer's name 108 | // This is called in Caller's thread 109 | } 110 | 111 | 112 | ```` 113 | 114 | ### More examples 115 | https://github.com/zakgof/actr/tree/master/src/example/java/com/zakgof/actr/example 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'maven-publish' 4 | id 'signing' 5 | id 'jacoco' 6 | id 'com.adarshr.test-logger' version '3.2.0' 7 | id 'biz.aQute.bnd.builder' version '6.3.1' 8 | id 'org.sonarqube' version '3.4.0.2513' 9 | } 10 | 11 | def ossrhUser = hasProperty('ossrhUsername') ? ossrhUsername : System.getenv('ossrhUsername') 12 | def ossrhPass = hasProperty('ossrhPassword') ? ossrhPassword : System.getenv('ossrhPassword') 13 | 14 | compileJava.options.encoding = 'UTF-8' 15 | sourceCompatibility = 11 16 | targetCompatibility = 11 17 | 18 | group = 'com.github.zakgof' 19 | archivesBaseName = 'actr' 20 | version = '0.4.3-SNAPSHOT' 21 | 22 | ext { 23 | descr = 'Simple Java actor model implementation' 24 | } 25 | 26 | repositories { 27 | mavenCentral() 28 | } 29 | 30 | dependencies { 31 | testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' 32 | } 33 | 34 | sourceSets { 35 | test { 36 | java { 37 | srcDirs = ['src/example/java', 'src/test/java'] 38 | } 39 | } 40 | } 41 | 42 | jar { 43 | bnd('-exportcontents': 'com.zakgof.actr') 44 | } 45 | 46 | java { 47 | withJavadocJar() 48 | withSourcesJar() 49 | } 50 | 51 | test { 52 | useJUnitPlatform() 53 | } 54 | 55 | artifacts { 56 | archives javadocJar, sourcesJar 57 | } 58 | 59 | publishing { 60 | publications { 61 | mavenJava(MavenPublication){ 62 | artifactId = archivesBaseName 63 | groupId = group 64 | version = version 65 | from(components["java"]) 66 | pom { 67 | name = 'actr' 68 | description = 'Simple Java actor model implementation' 69 | url = 'https://github.com/zakgof/actr' 70 | 71 | scm { 72 | connection = 'scm:git:https://github.com/zakgof/actr.git' 73 | developerConnection = 'scm:git:https://github.com/zakgof/actr.git' 74 | url = 'https://github.com/zakgof/actr/tree/master/actr' 75 | } 76 | 77 | licenses { 78 | license { 79 | name = 'The Apache License, Version 2.0' 80 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 81 | } 82 | } 83 | 84 | developers { 85 | developer { 86 | id = 'zakgof' 87 | name = 'Oleksandr Zakusylo' 88 | email = 'zakgof@gmail.com' 89 | } 90 | } 91 | } 92 | } 93 | } 94 | repositories { 95 | maven { 96 | url = 'https://oss.sonatype.org/content/repositories/snapshots' 97 | credentials { 98 | username = ossrhUser 99 | password = ossrhPass 100 | } 101 | } 102 | mavenLocal() 103 | } 104 | } 105 | 106 | signing { 107 | sign publishing.publications.mavenJava 108 | } 109 | 110 | test { 111 | finalizedBy jacocoTestReport 112 | } 113 | 114 | jacocoTestReport { 115 | reports { 116 | xml.enabled true 117 | } 118 | } 119 | 120 | sonarqube { 121 | properties { 122 | property "sonar.projectKey", "zakgof_actr" 123 | property "sonar.organization", "zakgof" 124 | property "sonar.host.url", "https://sonarcloud.io" 125 | property "sonar.login", project.hasProperty('sonarlogin') ? sonarlogin : null 126 | } 127 | } 128 | 129 | tasks['sonarqube'].dependsOn test 130 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakgof/actr/384a18ebc4a9f19438eb00c27e3f71586442c070/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/example/java/com/zakgof/actr/example/ActrForkJoinMergeSort.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.example; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.Random; 6 | import java.util.function.Consumer; 7 | import java.util.stream.IntStream; 8 | 9 | import com.zakgof.actr.Actr; 10 | import com.zakgof.actr.IActorRef; 11 | import com.zakgof.actr.IActorSystem; 12 | 13 | public class ActrForkJoinMergeSort { 14 | 15 | public static void main(String[] args) throws InterruptedException { 16 | Random random = new Random(0L); 17 | int[] input = IntStream.range(0, 1 << 10).map(i -> random.nextInt(1 << 20)).toArray(); 18 | System.err.println("Actr merge sort started..."); 19 | long start = System.currentTimeMillis(); 20 | sort(input); 21 | long end = System.currentTimeMillis(); 22 | System.err.println("finished in " + (end - start)); 23 | } 24 | 25 | public static void sort(int[] input) { 26 | 27 | final IActorSystem system = Actr.newSystem("actrsort"); 28 | 29 | final IActorRef master = system.actorOf(MasterActor::new, "master"); 30 | master.tell(m -> m.start(input)); 31 | system.shutdownCompletable().join(); 32 | } 33 | 34 | private static class MasterActor { 35 | 36 | public void start(int[] array) { 37 | IActorRef sorter = Actr.system().actorOf(Sorter::new, "c"); 38 | sorter. ask((s, cb) -> s.run(array, cb), this::result); 39 | } 40 | 41 | public void result(int[] array) { 42 | Actr.system().shutdown(); 43 | System.err.println(Arrays.toString(array)); 44 | } 45 | } 46 | 47 | private static class Sorter { 48 | 49 | public void run(int[] array, Consumer callback) { 50 | if (array.length == 1) { 51 | callback.accept(array); 52 | } else { 53 | int[] left = Arrays.copyOfRange(array, 0, array.length / 2); 54 | int[] right = Arrays.copyOfRange(array, array.length / 2, array.length); 55 | 56 | Actr.system(). forkBuilder(Arrays.asList(0, 1)).constructor(id -> new Sorter()). ask((id, sorter, cb) -> sorter.run(id == 0 ? left : right, cb), map -> join(map, callback)); 57 | } 58 | } 59 | 60 | private void join(Map map, Consumer callback) { 61 | int[] resultarray = merge(map.get(0), map.get(1)); 62 | callback.accept(resultarray); 63 | } 64 | 65 | public static int[] merge(int[] a, int[] b) { 66 | int[] answer = new int[a.length + b.length]; 67 | int i = a.length - 1, j = b.length - 1, k = answer.length; 68 | while (k > 0) 69 | answer[--k] = (j < 0 || (i >= 0 && a[i] >= b[j])) ? a[i--] : b[j--]; 70 | return answer; 71 | } 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/example/java/com/zakgof/actr/example/AskTellLaterExample.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.example; 2 | 3 | import java.util.Random; 4 | 5 | import com.zakgof.actr.Actr; 6 | import com.zakgof.actr.IActorRef; 7 | import com.zakgof.actr.IActorSystem; 8 | 9 | public class AskTellLaterExample { 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | final IActorSystem system = Actr.newSystem("example"); 13 | final IActorRef printerActor = system.actorOf(Printer::new); 14 | final IActorRef randomizerActor = system.actorOf(Randomizer::new); 15 | final IActorRef looperActor = system.actorOf(() -> new Looper(printerActor, randomizerActor)); 16 | 17 | looperActor.tell(Looper::run); 18 | 19 | system.shutdownCompletable().join(); 20 | 21 | } 22 | 23 | private static class Printer { 24 | public void print(String s) { 25 | System.err.println("[Printer] " + s); 26 | } 27 | } 28 | 29 | private static class Randomizer { 30 | 31 | private final Random random = new Random(); 32 | 33 | public int random() { 34 | System.err.println("[Randomizer] >>> "); 35 | try { 36 | return random.nextInt(10000); 37 | } finally { 38 | System.err.println("[Randomizer] <<< "); 39 | } 40 | } 41 | } 42 | 43 | public static class Looper { 44 | 45 | private final IActorRef printerActor; 46 | private final IActorRef randomizerActor; 47 | 48 | private int iteration = 0; 49 | 50 | public Looper(IActorRef printerActor, IActorRef randomizerActor) { 51 | this.printerActor = printerActor; 52 | this.randomizerActor = randomizerActor; 53 | } 54 | 55 | private void run() { 56 | iteration++; 57 | printerActor.tell(printer -> printer.print("Looper: iteration " + iteration)); 58 | randomizerActor.ask(Randomizer::random, this::showRandom); 59 | if (iteration < 10) { 60 | Actr. current().later(Looper::run, 1000); 61 | } else { 62 | Actr.system().shutdown(); 63 | } 64 | } 65 | 66 | private void showRandom(int rand) { 67 | printerActor.tell(printer -> printer.print("Looper: Randomizer returned: " + rand)); 68 | } 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/example/java/com/zakgof/actr/vsakka/ActrQuickstart.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.vsakka; 2 | 3 | import java.io.IOException; 4 | 5 | import com.zakgof.actr.Actr; 6 | import com.zakgof.actr.IActorRef; 7 | import com.zakgof.actr.IActorSystem; 8 | 9 | public class ActrQuickstart { 10 | 11 | public static void main(String[] args) { 12 | final IActorSystem system = Actr.newSystem("helloactr"); 13 | try { 14 | // #create-actors 15 | final IActorRef printerActor = system.actorOf(Printer::new, "printerActor"); 16 | final IActorRef howdyGreeter = system.actorOf(() -> new Greeter("Howdy", printerActor), "howdyGreeter"); 17 | final IActorRef helloGreeter = system.actorOf(() -> new Greeter("Hello", printerActor), "helloGreeter"); 18 | final IActorRef goodDayGreeter = system.actorOf(() -> new Greeter("Good day", printerActor), "goodDayGreeter"); 19 | // #create-actors 20 | 21 | // #main-send-messages 22 | howdyGreeter.tell(gr -> gr.setWhoToGreet("Actr")); 23 | howdyGreeter.tell(Greeter::greet); 24 | 25 | howdyGreeter.tell(gr -> gr.setWhoToGreet("Zakgof")); 26 | howdyGreeter.tell(Greeter::greet); 27 | 28 | helloGreeter.tell(gr -> gr.setWhoToGreet("Java")); 29 | helloGreeter.tell(Greeter::greet); 30 | 31 | goodDayGreeter.tell(gr -> gr.setWhoToGreet("Lambda")); 32 | goodDayGreeter.tell(Greeter::greet); 33 | // #main-send-messages 34 | 35 | System.out.println(">>> Press ENTER to exit <<<"); 36 | System.in.read(); 37 | } catch (IOException ioe) { 38 | } finally { 39 | system.shutdown(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/example/java/com/zakgof/actr/vsakka/Greeter.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.vsakka; 2 | 3 | import com.zakgof.actr.IActorRef; 4 | 5 | public class Greeter { 6 | 7 | private String message; 8 | private IActorRef printerActor; 9 | private String greeting; 10 | 11 | public Greeter(String message, IActorRef printerActor) { 12 | this.message = message; 13 | this.printerActor = printerActor; 14 | } 15 | 16 | public void setWhoToGreet(String whoToGreet) { 17 | this.greeting = message + ", " + whoToGreet; 18 | } 19 | 20 | public void greet() { 21 | String greetingMsg = greeting; 22 | printerActor.tell(printer -> printer.print(greetingMsg)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/example/java/com/zakgof/actr/vsakka/Printer.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.vsakka; 2 | 3 | public class Printer { 4 | 5 | public void print(String greeting) { 6 | System.err.println(greeting); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/Actr.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import com.zakgof.actr.impl.ActorSystemImpl; 4 | 5 | /** 6 | * Helper class to work with actor contexts 7 | */ 8 | public class Actr { 9 | 10 | private static ThreadLocal> currentActor = new ThreadLocal<>(); 11 | private static ThreadLocal> callerActor = new ThreadLocal<>(); 12 | 13 | private Actr() { 14 | } 15 | 16 | /** 17 | * Create a new actor system with the specified name. 18 | * 19 | * Default ForkJoinScheduler is used. 20 | * 21 | * @param name actor system name 22 | * @return newly created actor system 23 | */ 24 | public static IActorSystem newSystem(String name) { 25 | return new ActorSystemImpl(name); 26 | } 27 | 28 | /** 29 | * Create a new actor system with the specified name and scheduler factory. 30 | * 31 | * Scheduler factory will be used to create actors; actor will own the scheduler, i.e. each scheduler is disposed together with its owning actor. 32 | * 33 | * @param name actor system name 34 | * @param defaultScheduler default scheduler for new actors 35 | * @return newly created actor system 36 | */ 37 | public static IActorSystem newSystem(String name, IActorScheduler defaultScheduler) { 38 | return new ActorSystemImpl(name, defaultScheduler); 39 | } 40 | 41 | /** 42 | * Gets the reference to the actor from its code. 43 | * 44 | * When called from a properly called actor action, return this actor's ActorRef. 45 | * 46 | * Returns null if called not from from actor context 47 | * 48 | * @param actor POJO class 49 | * @return {@link IActorRef} for the actor being called 50 | */ 51 | @SuppressWarnings("unchecked") 52 | public static IActorRef current() { 53 | return (IActorRef) currentActor.get(); 54 | } 55 | 56 | /** 57 | * Gets the reference to the actor calling this actor. 58 | * 59 | * When called from a properly called actor tell/ask/later action, return the actor from which context this actor's action was called. 60 | * 61 | * If called in a callback for {@link IActorRef#ask} calls, this method returns a reference to the 'asked' actor. 62 | * 63 | * For ask/later calls not from actor context, this method returns null 64 | * 65 | * @param actor POJO class 66 | * @return {@link IActorRef} for the caller actor, or null if called not from actor context or from an actor called from outside any actor context 67 | */ 68 | @SuppressWarnings("unchecked") 69 | public static IActorRef caller() { 70 | return (IActorRef) callerActor.get(); 71 | } 72 | 73 | /** 74 | * Returns the current actor's system. 75 | * 76 | * @return current actor system or null if called not from actor context 77 | */ 78 | public static IActorSystem system() { 79 | IActorRef actor = current(); 80 | return actor == null ? null : current().system(); 81 | } 82 | 83 | /* Not a part of public API */ 84 | public static void setCurrent(IActorRef actor) { 85 | if (actor == null) { 86 | currentActor.remove(); 87 | } else { 88 | currentActor.set(actor); 89 | } 90 | } 91 | 92 | /* Not a part of public API */ 93 | public static void setCaller(IActorRef actor) { 94 | if (actor == null) { 95 | callerActor.remove(); 96 | } else { 97 | callerActor.set(actor); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/IActorBuilder.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | import java.util.function.Supplier; 6 | 7 | public interface IActorBuilder { 8 | 9 | /** 10 | * Adds an existing actor POJO class instance to be used with the actor being constructed. 11 | * 12 | * @param object actor POJO class instance 13 | * @return this builder 14 | */ 15 | IActorBuilder object(T object); 16 | 17 | /** 18 | * Adds a factory for POJO class instance creation to be used with the actor being constructed. 19 | * 20 | * Constructor will be called during {@link #build()} call in a synchronous manner 21 | * 22 | * @param constructor POJO class instance factory 23 | * @return this builder 24 | */ 25 | IActorBuilder constructor(Supplier constructor); 26 | 27 | /** 28 | * Adds a destructor to be called in actor thread context when the actor is being destroyed. 29 | * 30 | * @param destructor action to be called on actor destruction 31 | * @return this builder 32 | */ 33 | IActorBuilder destructor(Consumer destructor); 34 | 35 | /** 36 | * Sets a name for the actor being constructed. 37 | * 38 | * @param name actor name 39 | * @return this builder 40 | */ 41 | IActorBuilder name(String name); 42 | 43 | /** 44 | * Sets a scheduler for the actor being constructed. 45 | * 46 | * @param scheduler scheduler to be used for the actor being constructed 47 | * @return this builder 48 | */ 49 | IActorBuilder scheduler(IActorScheduler scheduler); 50 | 51 | /** 52 | * Sets an exception handler for the actor being constructed. 53 | * 54 | * Exception handler is triggered in actor's thread context whenever an exception occurs in actor's ask, tell or later call. Note that the exception handler is ignored when calling methods returning CallableFuture as in that 55 | * case the exception is passed directly to the future. 56 | * 57 | * @param exceptionHandler exception handler to be triggered 58 | * @return this builder 59 | */ 60 | IActorBuilder exceptionHandler(BiConsumer exceptionHandler); 61 | 62 | /** 63 | * Creates an actor using this builder. 64 | * 65 | * @return newly create ActorRef instance 66 | */ 67 | IActorRef build(); 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/IActorRef.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.Consumer; 6 | import java.util.function.Function; 7 | 8 | /** 9 | * Interface for addressing actors. 10 | * 11 | * @param actor POJO class 12 | */ 13 | public interface IActorRef extends AutoCloseable { 14 | 15 | /** 16 | * @return actor's @link {ActorSystem} 17 | */ 18 | IActorSystem system(); 19 | 20 | /** 21 | * Sends a message to the actor defined by this reference. 22 | * 23 | * The specified action is executed on the actor's object asynchronously in actor's thread context. This method does not wait for completion of the action, it returns immediately. 24 | * 25 | * @param action action to be executed on actor's object. 26 | */ 27 | void tell(Consumer action); 28 | 29 | /** 30 | * Schedules an action to be executed once after a specified time. 31 | * 32 | * The specified action is executed on the actor's object asynchronously in actor's thread context. 33 | * 34 | * @param action action to be executed on actor's object. 35 | * @param ms delay in milliseconds 36 | */ 37 | void later(Consumer action, long ms); 38 | 39 | /** 40 | * Sends a message to actor and gets a response. 41 | * 42 | * Performs the specified call on the actor's object asynchronously. The call is executed in this actor's thread context, the return value from the action is then passed to the consumer in the caller's actor thread context. If the method is 43 | * called not from actor's context, exception is thrown. 44 | * 45 | * This method does not wait for response, it returns immediately. 46 | * 47 | * @param actor call response class 48 | * @param action action to be executed on actor's object, return value will be the response 49 | * @param consumer consumer to receive the response 50 | */ 51 | void ask(Function action, Consumer consumer); 52 | 53 | /** 54 | * Sends a message to actor and gets a response. 55 | * 56 | * Performs the specified call on the actor's object asynchronously. The call is executed in this actor's thread context. The action get a response consumer as an additional parameter, it should pass the result to that consumer. The response in 57 | * passed to the caller's consumer in the caller's actor thread context. If the method is called not from any actor's context, consumer is executed in target actor's thread context. 58 | * 59 | * This method does not wait for response, it returns immediately. 60 | * 61 | * @param actor call response class 62 | * @param action action to be executed on actor's object; the BiConsumer accepts the actor's object and the callback to receive the actor's call result. 63 | * @param consumer consumer to receive the response 64 | */ 65 | void ask(BiConsumer> action, Consumer consumer); 66 | 67 | /** 68 | * Sends a message to actor and returns a CompletableFuture to be completed with the response value. 69 | * 70 | * Performs the specified call on the actor's object asynchronously. The call is executed in this actor's thread context, the future is then completed with the result value. 71 | * 72 | * It is allowed to call this method from outside actor context. In that case the CompletableFuture will be executed in target actor thread context. 73 | * 74 | * This method returns a CompletableFuture, which is completed with a result once the actor's call completes. If an exception occurs during actor's call, the exception is then passed to the CompletableFuture and the actor's exception handler is not triggered. Both successful and failed completions occur in the caller's actor thread context. 75 | * 76 | * @param actor call response class 77 | * @param action action to be executed on actor's object, return value will be the response 78 | * @return CompletableFuture to be completed with the actor's call result 79 | */ 80 | CompletableFuture ask(Function action); 81 | 82 | /** 83 | * Sends a message to actor and returns a CompletableFuture to be completed with the response value. 84 | * 85 | * Performs the specified call on the actor's object asynchronously. The call is executed in this actor's thread context, the future is then completed with the result value in the caller's actor thread context. If the method is 86 | * called not from actor's context, exception is thrown. 87 | * 88 | * This method returns a CompletableFuture, which is completed with a result once the actor's call completes. If an exception occurs during actor's call, the exception is then passed to the CompletableFuture and the actor's exception handler is not triggered. Both successful and failed completions occur in the caller's actor thread context. 89 | * 90 | * @param actor call response class 91 | * @param action action to be executed on actor's object; the BiConsumer accepts the actor's object and the callback to receive the actor's call result. 92 | * @return CompletableFuture to be completed with the actor's call result 93 | */ 94 | CompletableFuture ask(BiConsumer> action); 95 | 96 | /** 97 | * Destroy the actor. 98 | * 99 | * If defined, destructor will be called in actor's thread context. 100 | * 101 | * If actor had a dedicated scheduler, the scheduler will be destroyed as well. 102 | * 103 | * Messages pending for this actor will be processed before terminating (TODO ??) 104 | */ 105 | @Override 106 | void close(); 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/IActorScheduler.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | public interface IActorScheduler extends AutoCloseable { 4 | 5 | default void actorCreated(Object actorId) { 6 | } 7 | 8 | default void actorDisposed(Object actorId) { 9 | } 10 | 11 | void schedule(Runnable task, Object actorId); 12 | 13 | @Override 14 | default void close() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/IActorSystem.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import java.util.Collection; 4 | import java.util.concurrent.CompletableFuture; 5 | import java.util.function.Supplier; 6 | 7 | public interface IActorSystem { 8 | 9 | /** 10 | * Initiate an orderly shutdown of the actor system. 11 | * 12 | * The currently running actor actions are not terminated; new calls to actors are ignored, actor system triggers actor destructors in their respective thread contexts. 13 | * 14 | * Creating new actors under this system will fail after initiating the shutdown. 15 | * 16 | * Clients may use {@link #shutdownCompletable()} to be notified when the shutdown procedure completes. 17 | * 18 | * @return CompletableFuture that client may use to be notified when shutdown completes; the supplied string is shutdown reason 19 | */ 20 | CompletableFuture shutdown(); 21 | 22 | /** 23 | * @return a CompletableFuture to be triggered when actor system shutdown completes. The result value is the shutdown reason. 24 | */ 25 | CompletableFuture shutdownCompletable(); 26 | 27 | /** 28 | * Get an instance of {@link IActorBuilder} under this system. 29 | * 30 | * @param actor POJO class 31 | * @return actor builder instance 32 | */ 33 | IActorBuilder actorBuilder(); 34 | 35 | /** 36 | * Create a new actor under this system with a specified POJO instance factory and name. 37 | * 38 | * @param actor POJO class 39 | * @param constructor factory to create actor POJO class instance 40 | * @param name actor name 41 | * @return ActorRef actor reference 42 | */ 43 | IActorRef actorOf(Supplier constructor, String name); 44 | 45 | /** 46 | * Create a new actor under this system with a specified POJO instance factory and a autogenerated name. 47 | * 48 | * @param actor POJO class 49 | * @param constructor factory to create actor POJO class instance. 50 | * @return ActorRef actor reference 51 | */ 52 | IActorRef actorOf(Supplier constructor); 53 | 54 | IForkBuilder forkBuilder(Collection ids); 55 | 56 | String name(); 57 | } -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/IForkBuilder.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.function.BiFunction; 6 | import java.util.function.Consumer; 7 | import java.util.function.Function; 8 | 9 | import com.zakgof.actr.impl.ActorSystemImpl.TernaryConsumer; 10 | 11 | public interface IForkBuilder { 12 | 13 | void ask(TernaryConsumer> action, Consumer> result); 14 | 15 | void ask(BiFunction action, Consumer> result); 16 | 17 | IForkBuilder scheduler(Function scheduler); 18 | 19 | IForkBuilder constructor(Function constructor); 20 | 21 | IForkBuilder ids(Collection ids); 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/Schedulers.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.ForkJoinPool; 6 | import java.util.concurrent.ThreadFactory; 7 | 8 | import com.zakgof.actr.impl.BlockingThreadScheduler; 9 | import com.zakgof.actr.impl.ExecutorBasedScheduler; 10 | import com.zakgof.actr.impl.SingleThreadScheduler; 11 | import com.zakgof.actr.impl.ThreadPerActorScheduler; 12 | 13 | /** 14 | * Static factory to create schedulers. 15 | */ 16 | public class Schedulers { 17 | 18 | private Schedulers() { 19 | } 20 | 21 | /** 22 | * Creates a scheduler based on the shared ForkJoinPool. 23 | * @param throughput maximum number of pending actor messages to be processed at once 24 | * @return scheduler 25 | */ 26 | public static IActorScheduler newForkJoinPoolScheduler(int throughput) { 27 | return new ExecutorBasedScheduler(ForkJoinPool.commonPool(), throughput); 28 | } 29 | 30 | /** 31 | * Creates a scheduler based on a user-provided ExecuterService. 32 | * @param executorService executor service for scheduling the tasks 33 | * @param throughput maximum number of pending actor messages to be processed at once 34 | * @return scheduler 35 | */ 36 | public static IActorScheduler newExecutorBasedScheduler(ExecutorService executorService, int throughput) { 37 | return new ExecutorBasedScheduler(executorService, throughput); 38 | } 39 | 40 | /** 41 | * Creates a scheduler based on a thread pool with a fixed number of threads. 42 | * @param threads number of threads in the thread pool 43 | * @param throughput maximum number of pending actor messages to be processed at once 44 | * @return scheduler 45 | */ 46 | public static IActorScheduler newFixedThreadPoolScheduler(int threads, int throughput) { 47 | return new ExecutorBasedScheduler(Executors.newFixedThreadPool(threads, runnable -> new Thread(runnable, "actr:fixed")), throughput); 48 | } 49 | 50 | /** 51 | * Creates a scheduler that processed all the actors messages sequentially in a single 52 | * user-supplied thread. See {@link BlockingThreadScheduler} for details. 53 | * @return scheduler 54 | */ 55 | public static BlockingThreadScheduler newBlockingThreadScheduler() { 56 | return new BlockingThreadScheduler(); 57 | } 58 | 59 | /** 60 | * Creates a scheduler using a single thread for all actor calls. 61 | * @return scheduler 62 | */ 63 | public static IActorScheduler newSingleThreadScheduler() { 64 | return new SingleThreadScheduler(); 65 | } 66 | 67 | /** 68 | * Creates a scheduler that creates a single-thread executor for each actor. 69 | * @return scheduler 70 | */ 71 | public static IActorScheduler newThreadPerActorScheduler() { 72 | return new ThreadPerActorScheduler(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/ActorImpl.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.UUID; 4 | import java.util.concurrent.CompletableFuture; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.Consumer; 7 | import java.util.function.Function; 8 | import java.util.function.Supplier; 9 | 10 | import com.zakgof.actr.Actr; 11 | import com.zakgof.actr.IActorRef; 12 | import com.zakgof.actr.IActorScheduler; 13 | import com.zakgof.actr.IActorSystem; 14 | import com.zakgof.actr.impl.IRegSet.IRegistration; 15 | 16 | class ActorImpl implements IActorRef { 17 | 18 | private volatile T object; 19 | private final ActorSystemImpl actorSystem; 20 | private final IActorScheduler scheduler; 21 | private final String name; 22 | private final BiConsumer exceptionHandler; 23 | private final Consumer destructor; 24 | private volatile Object box; 25 | private volatile IRegistration reg; 26 | 27 | ActorImpl(T object, Supplier constructor, IActorScheduler scheduler, ActorSystemImpl actorSystem, String name, BiConsumer exceptionHandler, Consumer destructor) { 28 | this.actorSystem = actorSystem; 29 | this.exceptionHandler = exceptionHandler; 30 | this.name = name == null ? getClass().getSimpleName().toLowerCase() + "_" + UUID.randomUUID() : name; 31 | this.destructor = destructor; 32 | if (object != null) { 33 | this.object = object; 34 | } 35 | this.scheduler = scheduler; 36 | scheduler.actorCreated(this); 37 | if (constructor != null) { 38 | this.object = constructor.get(); 39 | } 40 | actorSystem.add(this); 41 | } 42 | 43 | @Override 44 | public void tell(Consumer action) { 45 | IActorRef caller = Actr.current(); 46 | scheduleCall(action, caller); 47 | } 48 | 49 | private void scheduleCall(Consumer action, IActorRef caller) { 50 | scheduleCallErrorAware(action, caller, e -> exceptionHandler.accept(object, e)); 51 | } 52 | 53 | private void scheduleCallErrorAware(Consumer action, IActorRef caller, Consumer exceptionCallback) { 54 | scheduler.schedule(() -> { 55 | Actr.setCurrent(this); 56 | Actr.setCaller(caller); 57 | try { 58 | if (object == null) 59 | return; 60 | action.accept(object); 61 | } catch (Exception e) { 62 | exceptionCallback.accept(e); 63 | } finally { 64 | Actr.setCurrent(null); 65 | Actr.setCaller(null); 66 | } 67 | }, this); 68 | } 69 | 70 | @Override 71 | public void later(Consumer action, long ms) { 72 | IActorRef caller = Actr.current(); 73 | actorSystem.later(() -> { 74 | if (object != null) { 75 | scheduleCall(action, caller); 76 | } 77 | }, ms); 78 | } 79 | 80 | @Override 81 | public void ask(BiConsumer> action, Consumer consumer) { 82 | IActorRef current = Actr.current(); 83 | Consumer completion = current == null 84 | ? consumer 85 | : result -> current.tell(c -> consumer.accept(result)); 86 | tell(target -> action.accept(target, completion)); 87 | } 88 | 89 | @Override 90 | public void ask(Function call, Consumer consumer) { 91 | ask((target, callback) -> callback.accept(call.apply(target)), consumer); 92 | } 93 | 94 | @Override 95 | public CompletableFuture ask(BiConsumer> action) { 96 | IActorRef current = Actr.current(); 97 | CompletableFuture future = new CompletableFuture<>(); 98 | Consumer completion = current == null 99 | ? future::complete 100 | : result -> current.tell(c -> future.complete(result)); 101 | Consumer failure = current == null 102 | ? future::completeExceptionally 103 | : exception -> current.tell(c -> future.completeExceptionally(exception)); 104 | scheduleCallErrorAware(target -> action.accept(target, completion), current, failure); 105 | return future; 106 | } 107 | 108 | @Override 109 | public CompletableFuture ask(Function action) { 110 | return ask((target, callback) -> callback.accept(action.apply(target))); 111 | } 112 | 113 | private static IActorRef safeCurrent() { 114 | IActorRef caller = Actr.current(); 115 | if (caller == null) 116 | throw new IllegalStateException("It is not allowed to call ask from non-actor context. There's no actor to receive the response"); 117 | return caller; 118 | } 119 | 120 | T object() { 121 | return object; 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "[" + actorSystem.name() + ":" + name + "]"; 127 | } 128 | 129 | @Override 130 | public IActorSystem system() { 131 | return actorSystem; 132 | } 133 | 134 | /** 135 | * Called internally from system 136 | */ 137 | void dispose(Runnable whenFinished) { 138 | tell(o -> { 139 | if (destructor != null) { 140 | try { 141 | destructor.accept(object); 142 | } catch (Exception ex) { 143 | ex.printStackTrace(); // TODO: logging 144 | } 145 | } 146 | ((ActorSystemImpl) system()).remove(this); 147 | scheduler.actorDisposed(this); 148 | object = null; 149 | whenFinished.run(); 150 | }); 151 | 152 | } 153 | 154 | @Override 155 | public void close() { 156 | dispose(() -> { 157 | }); 158 | } 159 | 160 | void box(Object box) { 161 | this.box = box; 162 | } 163 | 164 | Object box() { 165 | return box; 166 | } 167 | 168 | void reg(IRegistration reg) { 169 | this.reg = reg; 170 | } 171 | 172 | IRegistration reg() { 173 | return reg; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/ActorSystemImpl.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.Random; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | import java.util.function.BiConsumer; 13 | import java.util.function.BiFunction; 14 | import java.util.function.Consumer; 15 | import java.util.function.Function; 16 | import java.util.function.Supplier; 17 | 18 | import com.zakgof.actr.IActorBuilder; 19 | import com.zakgof.actr.IActorRef; 20 | import com.zakgof.actr.IActorScheduler; 21 | import com.zakgof.actr.IActorSystem; 22 | import com.zakgof.actr.IForkBuilder; 23 | import com.zakgof.actr.Schedulers; 24 | 25 | public class ActorSystemImpl implements IActorSystem { 26 | 27 | private static final int DEFAULT_FORKJOINSCHEDULER_THROUGHPUT = 10; 28 | 29 | private final IActorScheduler defaultScheduler; 30 | 31 | private final String name; 32 | private final IRegSet> actors = new FastRegSet<>(); 33 | private final ScheduledExecutorService timer; 34 | 35 | private final CompletableFuture terminator = new CompletableFuture<>(); 36 | private final AtomicBoolean isShuttingDown = new AtomicBoolean(); 37 | 38 | private volatile boolean isShutDown; 39 | 40 | public ActorSystemImpl(String name, IActorScheduler defaultScheduler) { 41 | this.name = name; 42 | this.defaultScheduler = defaultScheduler; 43 | this.timer = Executors.newSingleThreadScheduledExecutor(runnable -> { 44 | Thread thread = new Thread(runnable, "actr:" + name + ":timer"); 45 | thread.setPriority(8); 46 | return thread; 47 | }); 48 | } 49 | 50 | public ActorSystemImpl(String name) { 51 | this(name, Schedulers.newForkJoinPoolScheduler(DEFAULT_FORKJOINSCHEDULER_THROUGHPUT)); 52 | } 53 | 54 | @Override 55 | public String name() { 56 | return name; 57 | } 58 | 59 | /** 60 | * Initiate an orderly shutdown of the actor system. 61 | * 62 | * The currently running actor actions are not terminated; new calls to actors are ignored, actor system triggers actor destructors in their respective thread contexts. 63 | * 64 | * Creating new actors under this system will fail after initiating the shutdown. 65 | * 66 | * Clients may use {@link #shutdownCompletable()} to be notified when the shutdown procedure completes. 67 | * 68 | * @return CompletableFuture that client may use to be notified when shutdown completes; the supplied string is shutdown reason 69 | */ 70 | @Override 71 | public CompletableFuture shutdown() { 72 | if (isShuttingDown.compareAndSet(false, true)) { 73 | timer.execute(() -> { 74 | Collection> actorRefs = actors.copy(); 75 | if (actorRefs.isEmpty()) { 76 | internalShutdown(); 77 | return; 78 | } 79 | int[] actorsToGo = { actorRefs.size() }; 80 | for (ActorImpl actor : actorRefs) { 81 | actor.dispose(() -> timer.execute(() -> { 82 | actorsToGo[0]--; 83 | if (actorsToGo[0] == 0) { 84 | internalShutdown(); 85 | } 86 | })); 87 | } 88 | }); 89 | } 90 | return terminator; 91 | } 92 | 93 | private void internalShutdown() { 94 | timer.shutdownNow(); 95 | defaultScheduler.close(); 96 | isShutDown = true; 97 | terminator.complete("shutdown"); 98 | } 99 | 100 | /** 101 | * @return a CompletableFuture to be triggered when actor system shutdown completes. The result value is the shutdown reason. 102 | */ 103 | @Override 104 | public CompletableFuture shutdownCompletable() { 105 | return terminator; 106 | } 107 | 108 | void add(ActorImpl actorRef) { 109 | checkShutdown(); 110 | actorRef.reg(actors.add(actorRef)); 111 | } 112 | 113 | void remove(ActorImpl actorRef) { 114 | actorRef.reg().remove(); 115 | } 116 | 117 | private void checkShutdown() { 118 | if (isShuttingDown.get()) 119 | throw new RuntimeException("Cannot add actor: actor system shutdown in progress"); 120 | if (isShutDown) 121 | throw new RuntimeException("Cannot add actor: actor system is shut down"); 122 | } 123 | 124 | /** 125 | * Get an instance of {@link ActorBuilderImpl} under this system 126 | * 127 | * @param actor POJO class 128 | * @return ActorBuilder instance 129 | */ 130 | @Override 131 | public IActorBuilder actorBuilder() { 132 | return new ActorBuilderImpl<>(this); 133 | } 134 | 135 | /** 136 | * Create a new actor under this system with a specified POJO instance factory and name. 137 | * 138 | * @param actor POJO class 139 | * @param constructor factory to create actor POJO class instance 140 | * @param name actor name 141 | * @return ActorRef actor reference 142 | */ 143 | @Override 144 | public IActorRef actorOf(Supplier constructor, String name) { 145 | return this. actorBuilder().constructor(constructor).name(name).build(); 146 | } 147 | 148 | /** 149 | * Create a new actor under this system with a specified POJO instance factory and a autogenerated name. 150 | * 151 | * @param actor POJO class 152 | * @param constructor factory to create actor POJO class instance. 153 | * @return ActorRef actor reference 154 | */ 155 | @Override 156 | public IActorRef actorOf(Supplier constructor) { 157 | return actorOf(constructor, Long.toHexString(new Random().nextLong())); 158 | } 159 | 160 | public static class ActorBuilderImpl implements IActorBuilder { 161 | private ActorSystemImpl actorSystem; 162 | private T object; 163 | private Supplier constructor; 164 | private Consumer destructor; 165 | private IActorScheduler scheduler; 166 | private String name; 167 | private BiConsumer exceptionHandler; 168 | 169 | private ActorBuilderImpl(ActorSystemImpl actorSystem) { 170 | actorSystem.checkShutdown(); 171 | this.actorSystem = actorSystem; 172 | this.scheduler = actorSystem.defaultScheduler; 173 | this.exceptionHandler = (obj, ex) -> ex.printStackTrace(); 174 | } 175 | 176 | /** 177 | * Adds an existing actor POJO class instance to be used with the actor being constructed. 178 | * 179 | * @param object actor POJO class instance 180 | * @return this builder 181 | */ 182 | @Override 183 | public IActorBuilder object(T object) { 184 | this.object = object; 185 | return this; 186 | } 187 | 188 | /** 189 | * Adds a factory for POJO class instance creation to be used with the actor being constructed. 190 | * 191 | * Constructor will be called during {@link #build()} call in a synchronous manner 192 | * 193 | * @param constructor POJO class instance factory 194 | * @return this builder 195 | */ 196 | @Override 197 | public IActorBuilder constructor(Supplier constructor) { 198 | this.constructor = constructor; 199 | return this; 200 | } 201 | 202 | /** 203 | * Adds a destructor to be called in actor thread context when the actor is being destroyed. 204 | * 205 | * @param destructor action to be called on actor destruction 206 | * @return this builder 207 | */ 208 | @Override 209 | public IActorBuilder destructor(Consumer destructor) { 210 | this.destructor = destructor; 211 | return this; 212 | } 213 | 214 | /** 215 | * Sets a name for the actor being constructed. 216 | * 217 | * @param name actor name 218 | * @return this builder 219 | */ 220 | @Override 221 | public IActorBuilder name(String name) { 222 | this.name = name; 223 | return this; 224 | } 225 | 226 | /** 227 | * Sets a scheduler for the actor being constructed. 228 | * 229 | * @param scheduler scheduler to be used for the actor being constructed 230 | * @return this builder 231 | */ 232 | @Override 233 | public IActorBuilder scheduler(IActorScheduler scheduler) { 234 | this.scheduler = scheduler; 235 | return this; 236 | } 237 | 238 | /** 239 | * Sets an exception handler for the actor being constructed. 240 | * 241 | * Exception handler is triggered in actor's thread context whenever an exception occurs in actor's ask, tell or later call. Note that the exception handler is ignored when calling methods returning CallableFuture as in that case the exception is passed directly to the future. 242 | * 243 | * @param exceptionHandler exception handler to be triggered 244 | * @return this builder 245 | */ 246 | @Override 247 | public IActorBuilder exceptionHandler(BiConsumer exceptionHandler) { 248 | this.exceptionHandler = exceptionHandler; 249 | return this; 250 | } 251 | 252 | /** 253 | * Creates an actor using this builder. 254 | * 255 | * @return newly create ActorRef instance 256 | */ 257 | @Override 258 | public IActorRef build() { 259 | if (constructor != null && object != null) 260 | throw new IllegalArgumentException("Not allowed to provide both object and constructor"); 261 | if (constructor == null && object == null) 262 | throw new IllegalArgumentException("Provide either object or constructor"); 263 | 264 | return new ActorImpl<>(object, constructor, scheduler, actorSystem, name, exceptionHandler, destructor); 265 | } 266 | 267 | } 268 | 269 | void later(Runnable runnable, long ms) { 270 | if (!timer.isShutdown() && !timer.isTerminated()) { 271 | timer.schedule(runnable, ms, TimeUnit.MILLISECONDS); 272 | } 273 | } 274 | 275 | @Override 276 | public String toString() { 277 | return "ActorSystem " + name; 278 | } 279 | 280 | @Override 281 | public IForkBuilder forkBuilder(Collection ids) { 282 | return new ForkBuilderImpl().ids(ids); 283 | } 284 | 285 | public interface TernaryConsumer { 286 | void accept(A a, B b, C c); 287 | } 288 | 289 | private class ForkBuilderImpl implements IForkBuilder { 290 | 291 | private Collection ids; 292 | private Function constructor; 293 | private Function scheduler = i -> ActorSystemImpl.this.defaultScheduler; 294 | 295 | @Override 296 | public IForkBuilder ids(Collection ids) { 297 | this.ids = ids; 298 | return this; 299 | } 300 | 301 | @Override 302 | public IForkBuilder constructor(Function constructor) { 303 | this.constructor = constructor; 304 | return this; 305 | } 306 | 307 | @Override 308 | public IForkBuilder scheduler(Function scheduler) { 309 | this.scheduler = scheduler; 310 | return this; 311 | } 312 | 313 | @Override 314 | public void ask(BiFunction action, Consumer> result) { 315 | ask((id, pojo, resultConsumer) -> resultConsumer.accept(action.apply(id, pojo)), result); 316 | } 317 | 318 | @Override 319 | public void ask(TernaryConsumer> action, Consumer> result) { 320 | 321 | Map map = new ConcurrentHashMap<>(); 322 | for (I id : ids) { 323 | IActorRef actor = ActorSystemImpl.this.actorBuilder() 324 | .constructor(() -> constructor.apply(id)) 325 | .scheduler(scheduler.apply(id)) 326 | .build(); 327 | Consumer callback = r -> { 328 | map.put(id, r); 329 | if (map.size() == ids.size()) { 330 | result.accept(map); 331 | } 332 | }; 333 | actor.ask((target, c) -> action.accept(id, target, c), callback); 334 | } 335 | } 336 | } 337 | 338 | } 339 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/BlockingThreadScheduler.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | 6 | import com.zakgof.actr.IActorScheduler; 7 | 8 | /** 9 | * Scheduler that processed all the actors messages sequentially in a single user-supplied thread. Run {@link #start()} from a thread that should become the message processor. The thread will block waiting for messages; once the scheduler is disposed 10 | * by calling {@link #close()}, the {@link #start()} method returns and thread continues. 11 | */ 12 | public class BlockingThreadScheduler implements IActorScheduler { 13 | 14 | private final BlockingQueue queue = new LinkedBlockingQueue<>(); 15 | private Thread thread; 16 | 17 | @Override 18 | public void schedule(Runnable task, Object actorId) { 19 | queue.add(task); 20 | } 21 | 22 | @Override 23 | public void close() { 24 | thread.interrupt(); 25 | } 26 | 27 | /** 28 | * Starts message processing loop in the current thread. This method does not return until the scheduler is disposed by calling {@link #close()}. If {@link #schedule(Runnable, Object)} is called before start(), the message will be kept 29 | * in the queue. 30 | */ 31 | public void start() { 32 | this.thread = Thread.currentThread(); 33 | try { 34 | while (!thread.isInterrupted()) { 35 | Runnable job = queue.take(); 36 | job.run(); 37 | } 38 | } catch (InterruptedException e) { 39 | Thread.currentThread().interrupt(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/ConcurrentDoublyLinkedList.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | /* 4 | * Written by Doug Lea with assistance from members of JCP JSR-166 5 | * Expert Group and released to the private domain, as explained at 6 | * http://creativecommons.org/licenses/privatedomain 7 | */ 8 | 9 | import java.util.AbstractCollection; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.ConcurrentModificationException; 13 | import java.util.Deque; 14 | import java.util.Iterator; 15 | import java.util.NoSuchElementException; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | import java.util.function.IntFunction; 18 | 19 | /** 20 | * A concurrent linked-list implementation of a {@link Deque} (double-ended queue). Concurrent insertion, removal, and access operations execute safely across multiple threads. Iterators are weakly consistent, returning elements reflecting the 21 | * state of the deque at some point at or since the creation of the iterator. They do not throw {@link ConcurrentModificationException}, and may proceed concurrently with other operations. 22 | * 23 | *

24 | * This class and its iterators implement all of the optional methods of the {@link Collection} and {@link Iterator} interfaces. Like most other concurrent collection implementations, this class does not permit the use of null 25 | * elements. because some null arguments and return values cannot be reliably distinguished from the absence of elements. Arbitrarily, the {@link Collection#remove} method is mapped to removeFirstOccurrence, and {@link Collection#add} is 26 | * mapped to addLast. 27 | * 28 | *

29 | * Beware that, unlike in most collections, the size method is NOT a constant-time operation. Because of the asynchronous nature of these deques, determining the current number of elements requires a traversal of the elements. 30 | * 31 | *

32 | * This class is Serializable, but relies on default serialization mechanisms. Usually, it is a better idea for any serializable class using a ConcurrentLinkedDeque to instead serialize a snapshot of the elements obtained by method 33 | * toArray. 34 | * 35 | * @author Doug Lea 36 | * @param the type of elements held in this collection 37 | */ 38 | 39 | class ConcurrentDoublyLinkedList extends AbstractCollection 40 | implements java.io.Serializable { 41 | @Override 42 | public T[] toArray(IntFunction generator) { 43 | return super.toArray(generator); 44 | } 45 | 46 | /* 47 | * This is an adaptation of an algorithm described in Paul Martin's "A Practical Lock-Free Doubly-Linked List". Sun Labs Tech report. The basic idea is to primarily rely on next-pointers to ensure consistency. Prev-pointers are in part 48 | * optimistic, reconstructed using forward pointers as needed. The main forward list uses a variant of HM-list algorithm similar to the one used in ConcurrentSkipListMap class, but a little simpler. It is also basically similar to the approach in 49 | * Edya Ladan-Mozes and Nir Shavit "An Optimistic Approach to Lock-Free FIFO Queues" in DISC04. 50 | * 51 | * Quoting a summary in Paul Martin's tech report: 52 | * 53 | * All cleanups work to maintain these invariants: (1) forward pointers are the ground truth. (2) forward pointers to dead nodes can be improved by swinging them further forward around the dead node. (2.1) forward pointers are still correct when 54 | * pointing to dead nodes, and forward pointers from dead nodes are left as they were when the node was deleted. (2.2) multiple dead nodes may point forward to the same node. (3) backward pointers were correct when they were installed (3.1) 55 | * backward pointers are correct when pointing to any node which points forward to them, but since more than one forward pointer may point to them, the live one is best. (4) backward pointers that are out of date due to deletion point to a 56 | * deleted node, and need to point further back until they point to the live node that points to their source. (5) backward pointers that are out of date due to insertion point too far backwards, so shortening their scope (by searching forward) 57 | * fixes them. (6) backward pointers from a dead node cannot be "improved" since there may be no live node pointing forward to their origin. (However, it does no harm to try to improve them while racing with a deletion.) 58 | * 59 | * 60 | * Notation guide for local variables n, b, f : a node, its predecessor, and successor s : some other successor 61 | */ 62 | 63 | // Minor convenience utilities 64 | 65 | /** 66 | * Returns true if given reference is non-null and isn't a header, trailer, or marker. 67 | * 68 | * @param n (possibly null) node 69 | * @return true if n exists as a user node 70 | */ 71 | private static boolean usable(Node n) { 72 | return n != null && !n.isSpecial(); 73 | } 74 | 75 | /** 76 | * Throws NullPointerException if argument is null 77 | * 78 | * @param v the element 79 | */ 80 | private static void checkNullArg(Object v) { 81 | if (v == null) 82 | throw new NullPointerException(); 83 | } 84 | 85 | /** 86 | * Creates an array list and fills it with elements of this list. Used by toArray. 87 | * 88 | * @return the arrayList 89 | */ 90 | private ArrayList toArrayList() { 91 | ArrayList c = new ArrayList<>(); 92 | for (Node n = header.forward(); n != null; n = n.forward()) 93 | c.add(n.element); 94 | return c; 95 | } 96 | 97 | // Fields and constructors 98 | 99 | private static final long serialVersionUID = 876323262645176354L; 100 | 101 | /** 102 | * List header. First usable node is at header.forward(). 103 | */ 104 | private final Node header; 105 | 106 | /** 107 | * List trailer. Last usable node is at trailer.back(). 108 | */ 109 | private final Node trailer; 110 | 111 | /** 112 | * Constructs an empty deque. 113 | */ 114 | ConcurrentDoublyLinkedList() { 115 | Node h = new Node<>(null, null, null); 116 | Node t = new Node<>(null, null, h); 117 | h.setNext(t); 118 | header = h; 119 | trailer = t; 120 | } 121 | 122 | /** 123 | * Appends the given element to the end of this deque. This is identical in function to the add method. 124 | * 125 | * @param o the element to be inserted at the end of this deque. 126 | * @throws NullPointerException if the specified element is null 127 | */ 128 | private void addLast(E o) { 129 | checkNullArg(o); 130 | while (trailer.prepend(o) == null) 131 | ; 132 | } 133 | 134 | Node coolAdd(E element) { 135 | Node node; 136 | while ((node = trailer.prepend(element)) == null); 137 | return node; 138 | } 139 | 140 | /** 141 | * Appends the given element to the end of this deque. (Identical in function to the add method; included only for consistency.) 142 | * 143 | * @param o the element to be inserted at the end of this deque. 144 | * @return true always 145 | * @throws NullPointerException if the specified element is null 146 | */ 147 | private boolean offerLast(E o) { 148 | addLast(o); 149 | return true; 150 | } 151 | 152 | /** 153 | * Retrieves and removes the first element of this deque, or returns null if this deque is empty. 154 | * 155 | * @return the first element of this deque, or null if empty. 156 | */ 157 | private E pollFirst() { 158 | for (;;) { 159 | Node n = header.successor(); 160 | if (!usable(n)) 161 | return null; 162 | if (n.delete()) 163 | return n.element; 164 | } 165 | } 166 | 167 | @Override 168 | public boolean add(E e) { 169 | return offerLast(e); 170 | } 171 | 172 | /** 173 | * Removes the first element e such that o.equals(e), if such an element exists in this deque. If the deque does not contain the element, it is unchanged. 174 | * 175 | * @param o element to be removed from this deque, if present. 176 | * @return true if the deque contained the specified element. 177 | * @throws NullPointerException if the specified element is null 178 | */ 179 | private boolean removeFirstOccurrence(Object o) { 180 | checkNullArg(o); 181 | for (;;) { 182 | Node n = header.forward(); 183 | for (;;) { 184 | if (n == null) 185 | return false; 186 | if (o.equals(n.element)) { 187 | if (n.delete()) 188 | return true; 189 | else 190 | break; // restart if interference 191 | } 192 | n = n.forward(); 193 | } 194 | } 195 | } 196 | 197 | /** 198 | * Returns true if this deque contains at least one element e such that o.equals(e). 199 | * 200 | * @param o element whose presence in this deque is to be tested. 201 | * @return true if this deque contains the specified element. 202 | */ 203 | @Override 204 | public boolean contains(Object o) { 205 | if (o == null) 206 | return false; 207 | for (Node n = header.forward(); n != null; n = n.forward()) 208 | if (o.equals(n.element)) 209 | return true; 210 | return false; 211 | } 212 | 213 | /** 214 | * Returns true if this collection contains no elements. 215 | *

216 | * 217 | * @return true if this collection contains no elements. 218 | */ 219 | @Override 220 | public boolean isEmpty() { 221 | return !usable(header.successor()); 222 | } 223 | 224 | /** 225 | * Returns the number of elements in this deque. If this deque contains more than Integer.MAX_VALUE elements, it returns Integer.MAX_VALUE. 226 | * 227 | *

228 | * Beware that, unlike in most collections, this method is NOT a constant-time operation. Because of the asynchronous nature of these deques, determining the current number of elements requires traversing them all to count them. 229 | * Additionally, it is possible for the size to change during execution of this method, in which case the returned result will be inaccurate. Thus, this method is typically not very useful in concurrent applications. 230 | * 231 | * @return the number of elements in this deque. 232 | */ 233 | @Override 234 | public int size() { 235 | long count = 0; 236 | for (Node n = header.forward(); n != null; n = n.forward()) 237 | ++count; 238 | return (count >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) count; 239 | } 240 | 241 | /** 242 | * Removes the first element e such that o.equals(e), if such an element exists in this deque. If the deque does not contain the element, it is unchanged. 243 | * 244 | * @param o element to be removed from this deque, if present. 245 | * @return true if the deque contained the specified element. 246 | * @throws NullPointerException if the specified element is null 247 | */ 248 | @Override 249 | public boolean remove(Object o) { 250 | return removeFirstOccurrence(o); 251 | } 252 | 253 | /** 254 | * Appends all of the elements in the specified collection to the end of this deque, in the order that they are returned by the specified collection's iterator. The behavior of this operation is undefined if the specified collection is modified 255 | * while the operation is in progress. (This implies that the behavior of this call is undefined if the specified Collection is this deque, and this deque is nonempty.) 256 | * 257 | * @param c the elements to be inserted into this deque. 258 | * @return true if this deque changed as a result of the call. 259 | * @throws NullPointerException if c or any element within it is null 260 | */ 261 | @Override 262 | public boolean addAll(Collection c) { 263 | Iterator it = c.iterator(); 264 | if (!it.hasNext()) 265 | return false; 266 | do { 267 | addLast(it.next()); 268 | } while (it.hasNext()); 269 | return true; 270 | } 271 | 272 | /** 273 | * Removes all of the elements from this deque. 274 | */ 275 | @Override 276 | public void clear() { 277 | while (pollFirst() != null) 278 | ; 279 | } 280 | 281 | /** 282 | * Returns an array containing all of the elements in this deque in the correct order. 283 | * 284 | * @return an array containing all of the elements in this deque in the correct order. 285 | */ 286 | @Override 287 | public Object[] toArray() { 288 | return toArrayList().toArray(); 289 | } 290 | 291 | /** 292 | * Returns an array containing all of the elements in this deque in the correct order; the runtime type of the returned array is that of the specified array. If the deque fits in the specified array, it is returned therein. Otherwise, a new array 293 | * is allocated with the runtime type of the specified array and the size of this deque. 294 | *

295 | * 296 | * If the deque fits in the specified array with room to spare (i.e., the array has more elements than the deque), the element in the array immediately following the end of the collection is set to null. This is useful in determining the length 297 | * of the deque only if the caller knows that the deque does not contain any null elements. 298 | * 299 | * @param a the array into which the elements of the deque are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose. 300 | * @return an array containing the elements of the deque. 301 | * @throws ArrayStoreException if the runtime type of a is not a supertype of the runtime type of every element in this deque. 302 | * @throws NullPointerException if the specified array is null. 303 | */ 304 | @Override 305 | public T[] toArray(T[] a) { 306 | return toArrayList().toArray(a); 307 | } 308 | 309 | /** 310 | * Returns a weakly consistent iterator over the elements in this deque, in first-to-last order. The next method returns elements reflecting the state of the deque at some point at or since the creation of the iterator. The method does 311 | * not throw {@link ConcurrentModificationException}, and may proceed concurrently with other operations. 312 | * 313 | * @return an iterator over the elements in this deque 314 | */ 315 | @Override 316 | public Iterator iterator() { 317 | return new CLDIterator(); 318 | } 319 | 320 | final class CLDIterator implements Iterator { 321 | Node last; 322 | 323 | Node next = header.forward(); 324 | 325 | @Override 326 | public boolean hasNext() { 327 | return next != null; 328 | } 329 | 330 | @Override 331 | public E next() { 332 | Node l = last = next; 333 | if (l == null) 334 | throw new NoSuchElementException(); 335 | next = next.forward(); 336 | return l.element; 337 | } 338 | 339 | @Override 340 | public void remove() { 341 | Node l = last; 342 | if (l == null) 343 | throw new IllegalStateException(); 344 | while (!l.delete() && !l.isDeleted()) 345 | ; 346 | } 347 | } 348 | 349 | } 350 | 351 | /** 352 | * Linked Nodes. As a minor efficiency hack, this class opportunistically inherits from AtomicReference, with the atomic ref used as the "next" link. 353 | * 354 | * Nodes are in doubly-linked lists. There are three kinds of special nodes, distinguished by: * The list header has a null prev link * The list trailer has a null next link * A deletion marker has a prev link pointing to itself. All three kinds of 355 | * special nodes have null element fields. 356 | * 357 | * Regular nodes have non-null element, next, and prev fields. To avoid visible inconsistencies when deletions overlap element replacement, replacements are done by replacing the node, not just setting the element. 358 | * 359 | * Nodes can be traversed by read-only ConcurrentLinkedDeque class operations just by following raw next pointers, so long as they ignore any special nodes seen along the way. (This is automated in method forward.) However, traversal using prev 360 | * pointers is not guaranteed to see all live nodes since a prev pointer of a deleted node can become unrecoverably stale. 361 | */ 362 | 363 | @SuppressWarnings("serial") 364 | class Node extends AtomicReference> { 365 | private volatile Node prev; 366 | 367 | final E element; 368 | 369 | /** Creates a node with given contents */ 370 | Node(E element, Node next, Node prev) { 371 | super(next); 372 | this.prev = prev; 373 | this.element = element; 374 | } 375 | 376 | /** Creates a marker node with given successor */ 377 | Node(Node next) { 378 | super(next); 379 | this.prev = this; 380 | this.element = null; 381 | } 382 | 383 | /** 384 | * Gets next link (which is actually the value held as atomic reference). 385 | */ 386 | private Node getNext() { 387 | return get(); 388 | } 389 | 390 | /** 391 | * Sets next link 392 | * 393 | * @param n the next node 394 | */ 395 | void setNext(Node n) { 396 | set(n); 397 | } 398 | 399 | /** 400 | * compareAndSet next link 401 | */ 402 | private boolean casNext(Node cmp, Node val) { 403 | return compareAndSet(cmp, val); 404 | } 405 | 406 | /** 407 | * Gets prev link 408 | */ 409 | private Node getPrev() { 410 | return prev; 411 | } 412 | 413 | /** 414 | * Sets prev link 415 | * 416 | * @param b the previous node 417 | */ 418 | void setPrev(Node b) { 419 | prev = b; 420 | } 421 | 422 | /** 423 | * Returns true if this is a header, trailer, or marker node 424 | */ 425 | boolean isSpecial() { 426 | return element == null; 427 | } 428 | 429 | /** 430 | * Returns true if this is a trailer node 431 | */ 432 | boolean isTrailer() { 433 | return getNext() == null; 434 | } 435 | 436 | /** 437 | * Returns true if this is a header node 438 | */ 439 | boolean isHeader() { 440 | return getPrev() == null; 441 | } 442 | 443 | /** 444 | * Returns true if this is a marker node 445 | */ 446 | boolean isMarker() { 447 | return getPrev() == this; 448 | } 449 | 450 | /** 451 | * Returns true if this node is followed by a marker, meaning that it is deleted. 452 | * 453 | * @return true if this node is deleted 454 | */ 455 | boolean isDeleted() { 456 | Node f = getNext(); 457 | return f != null && f.isMarker(); 458 | } 459 | 460 | /** 461 | * Returns next node, ignoring deletion marker 462 | */ 463 | private Node nextNonmarker() { 464 | Node f = getNext(); 465 | return (f == null || !f.isMarker()) ? f : f.getNext(); 466 | } 467 | 468 | /** 469 | * Returns the next non-deleted node, swinging next pointer around any encountered deleted nodes, and also patching up successor''s prev link to point back to this. Returns null if this node is trailer so has no successor. 470 | * 471 | * @return successor, or null if no such 472 | */ 473 | Node successor() { 474 | Node f = nextNonmarker(); 475 | for (;;) { 476 | if (f == null) 477 | return null; 478 | if (!f.isDeleted()) { 479 | if (f.getPrev() != this && !isDeleted()) 480 | f.setPrev(this); // relink f's prev 481 | return f; 482 | } 483 | Node s = f.nextNonmarker(); 484 | if (f == getNext()) 485 | casNext(f, s); // unlink f 486 | f = s; 487 | } 488 | } 489 | 490 | /** 491 | * Returns the apparent predecessor of target by searching forward for it starting at this node, patching up pointers while traversing. Used by predecessor(). 492 | * 493 | * @return target's predecessor, or null if not found 494 | */ 495 | private Node findPredecessorOf(Node target) { 496 | Node n = this; 497 | for (;;) { 498 | Node f = n.successor(); 499 | if (f == target) 500 | return n; 501 | if (f == null) 502 | return null; 503 | n = f; 504 | } 505 | } 506 | 507 | /** 508 | * Returns the previous non-deleted node, patching up pointers as needed. Returns null if this node is header so has no successor. May also return null if this node is deleted, so doesn't have a distinct predecessor. 509 | * 510 | * @return predecessor or null if not found 511 | */ 512 | Node predecessor() { 513 | Node n = this; 514 | for (;;) { 515 | Node b = n.getPrev(); 516 | if (b == null) 517 | return n.findPredecessorOf(this); 518 | Node s = b.getNext(); 519 | if (s == this) 520 | return b; 521 | if (s == null || !s.isMarker()) { 522 | Node p = b.findPredecessorOf(this); 523 | if (p != null) 524 | return p; 525 | } 526 | n = b; 527 | } 528 | } 529 | 530 | /** 531 | * Returns the next node containing a nondeleted user element. Use for forward list traversal. 532 | * 533 | * @return successor, or null if no such 534 | */ 535 | Node forward() { 536 | Node f = successor(); 537 | return (f == null || f.isSpecial()) ? null : f; 538 | } 539 | 540 | /** 541 | * Returns previous node containing a nondeleted user element, if possible. Use for backward list traversal, but beware that if this method is called from a deleted node, it might not be able to determine a usable predecessor. 542 | * 543 | * @return predecessor, or null if no such could be found 544 | */ 545 | Node back() { 546 | Node f = predecessor(); 547 | return (f == null || f.isSpecial()) ? null : f; 548 | } 549 | 550 | /** 551 | * Tries to insert a node holding element as successor, failing if this node is deleted. 552 | * 553 | * @param element the element 554 | * @return the new node, or null on failure. 555 | */ 556 | Node append(E element) { 557 | for (;;) { 558 | Node f = getNext(); 559 | if (f == null || f.isMarker()) 560 | return null; 561 | Node x = new Node<>(element, f, this); 562 | if (casNext(f, x)) { 563 | f.setPrev(x); // optimistically link 564 | return x; 565 | } 566 | } 567 | } 568 | 569 | /** 570 | * Tries to insert a node holding element as predecessor, failing if no live predecessor can be found to link to. 571 | * 572 | * @param element the element 573 | * @return the new node, or null on failure. 574 | */ 575 | Node prepend(E element) { 576 | for (;;) { 577 | Node b = predecessor(); 578 | if (b == null) 579 | return null; 580 | Node x = new Node<>(element, this, b); 581 | if (b.casNext(this, x)) { 582 | setPrev(x); // optimistically link 583 | return x; 584 | } 585 | } 586 | } 587 | 588 | 589 | /** 590 | * Tries to mark this node as deleted, failing if already deleted or if this node is header or trailer 591 | * 592 | * @return true if successful 593 | */ 594 | boolean delete() { 595 | Node b = getPrev(); 596 | Node f = getNext(); 597 | if (b != null && f != null && !f.isMarker() 598 | && casNext(f, new Node<>(f))) { 599 | if (b.casNext(this, f)) 600 | f.setPrev(b); 601 | return true; 602 | } 603 | return false; 604 | } 605 | 606 | /** 607 | * Tries to insert a node holding element to replace this node. failing if already deleted. 608 | * 609 | * @param newElement the new element 610 | * @return the new node, or null on failure. 611 | */ 612 | Node replace(E newElement) { 613 | for (;;) { 614 | Node b = getPrev(); 615 | Node f = getNext(); 616 | if (b == null || f == null || f.isMarker()) 617 | return null; 618 | Node x = new Node<>(newElement, f, b); 619 | if (casNext(f, new Node<>(x))) { 620 | b.successor(); // to relink b 621 | x.successor(); // to relink f 622 | return x; 623 | } 624 | } 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/ExecutorBasedScheduler.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import com.zakgof.actr.IActorScheduler; 9 | 10 | /** 11 | * Scheduler based on a provided executor service. 12 | */ 13 | public class ExecutorBasedScheduler implements IActorScheduler { 14 | 15 | private final int throughput; 16 | private final ExecutorService executor; 17 | 18 | private volatile boolean shutdown = false; 19 | 20 | public ExecutorBasedScheduler(ExecutorService executor, int throughput) { 21 | this.executor = executor; 22 | this.throughput = throughput; 23 | } 24 | 25 | private static class Mailbox { 26 | private final Queue queue = new ConcurrentLinkedQueue<>(); 27 | private final AtomicInteger queued = new AtomicInteger(0); 28 | } 29 | 30 | @Override 31 | public void actorCreated(Object actorId) { 32 | ((ActorImpl) actorId).box(new Mailbox()); 33 | } 34 | 35 | @Override 36 | public void actorDisposed(Object actorId) { 37 | ((ActorImpl) actorId).box(null); 38 | } 39 | 40 | @Override 41 | public void schedule(Runnable raw, Object actorId) { 42 | 43 | if (shutdown) { 44 | return; 45 | } 46 | 47 | Runnable task = () -> { 48 | Mailbox mailbox = (Mailbox) ((ActorImpl) actorId).box(); 49 | mailbox.queue.add(raw); 50 | int before = mailbox.queued.getAndIncrement(); 51 | if (before == 0) { 52 | processMailbox(mailbox); 53 | } 54 | }; 55 | executor.execute(task); 56 | } 57 | 58 | private void processMailbox(Mailbox mailbox) { 59 | int processed = 0; 60 | for (;;) { 61 | Runnable runnable = mailbox.queue.poll(); 62 | if (runnable == null) 63 | break; 64 | runnable.run(); 65 | processed++; 66 | if (processed >= throughput) 67 | break; 68 | } 69 | int remaining = mailbox.queued.addAndGet(-processed); 70 | if (remaining > 0) { 71 | executor.execute(() -> processMailbox(mailbox)); 72 | } 73 | } 74 | 75 | @Override 76 | public void close() { 77 | this.shutdown = true; 78 | executor.shutdown(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/FastRegSet.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Collection; 4 | 5 | class FastRegSet implements IRegSet { 6 | 7 | private final ConcurrentDoublyLinkedList list = new ConcurrentDoublyLinkedList<>(); 8 | 9 | @Override 10 | public IRegistration add(T element) { 11 | Node node = list.coolAdd(element); 12 | return () -> {while(!node.delete());}; 13 | } 14 | 15 | @Override 16 | public Collection copy() { 17 | return list; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/IRegSet.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Collection; 4 | 5 | interface IRegSet { 6 | 7 | interface IRegistration { 8 | void remove(); 9 | } 10 | 11 | IRegistration add(T element); 12 | 13 | Collection copy(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/MapRegSet.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | class MapRegSet implements IRegSet { 8 | 9 | private final Map map = new ConcurrentHashMap<>(); 10 | 11 | @Override 12 | public IRegistration add(T element) { 13 | map.put(element, element); 14 | return () -> map.remove(element); 15 | } 16 | 17 | @Override 18 | public Collection copy() { 19 | return map.keySet(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/SingleThreadScheduler.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | 6 | import com.zakgof.actr.IActorScheduler; 7 | 8 | /** 9 | * Scheduler that processed all the actors messages sequentially in a 10 | * single-thread executor service. 11 | */ 12 | public class SingleThreadScheduler implements IActorScheduler { 13 | 14 | private final ExecutorService executor; 15 | 16 | public SingleThreadScheduler() { 17 | this.executor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "actr:single")); 18 | } 19 | 20 | @Override 21 | public void schedule(Runnable task, Object actorId) { 22 | if (!executor.isShutdown() && !executor.isTerminated()) { 23 | executor.submit(task); 24 | } 25 | } 26 | 27 | @Override 28 | public void close() { 29 | executor.shutdown(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/zakgof/actr/impl/ThreadPerActorScheduler.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.impl; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ThreadFactory; 8 | 9 | import com.zakgof.actr.IActorScheduler; 10 | 11 | /** 12 | * Scheduler that creates a single-thread executor for each actor. 13 | */ 14 | public class ThreadPerActorScheduler implements IActorScheduler { 15 | 16 | private final Map executors = new ConcurrentHashMap<>(); 17 | 18 | @Override 19 | public void actorCreated(Object actorId) { 20 | executors.put(actorId, Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "actr:" + actorId))); 21 | } 22 | 23 | @Override 24 | public void actorDisposed(Object actorId) { 25 | ExecutorService service = executors.remove(actorId); 26 | service.shutdown(); 27 | } 28 | 29 | @Override 30 | public void schedule(Runnable task, Object actorId) { 31 | ExecutorService executor = executors.get(actorId); 32 | if (!executor.isShutdown()) { 33 | executor.execute(task); 34 | } 35 | } 36 | 37 | @Override 38 | public void close() { 39 | executors.values().forEach(ExecutorService::shutdown); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/zakgof/actr/test/BasicTest.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.test; 2 | 3 | import static com.zakgof.actr.test.BasicTest.CheckPoints.*; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertNull; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.fail; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import com.zakgof.actr.Actr; 18 | import com.zakgof.actr.IActorRef; 19 | import com.zakgof.actr.IActorSystem; 20 | import com.zakgof.actr.Schedulers; 21 | 22 | class BasicTest { 23 | 24 | private final IActorSystem system = Actr.newSystem("test", Schedulers.newThreadPerActorScheduler()); 25 | 26 | private final IActorRef master = system.actorBuilder() 27 | .constructor(Master::new) 28 | .build(); 29 | private IActorRef testActor; 30 | 31 | @BeforeEach 32 | void before() { 33 | CheckPoints.clean(); 34 | 35 | testActor = system.actorBuilder() 36 | .constructor(TestActor::new) 37 | .destructor(TestActor::destructor) 38 | .exceptionHandler(TestActor::err) 39 | .build(); 40 | } 41 | 42 | @Test 43 | void tell() { 44 | testActor.tell(TestActor::simple); 45 | system.shutdownCompletable().join(); 46 | validate(ActorConstructor, ActorCallSimple, ActorDestructor); 47 | } 48 | 49 | @Test 50 | void ask() { 51 | assertNull(Actr.caller()); 52 | assertNull(Actr.current()); 53 | master.tell(Master::ask47); 54 | system.shutdownCompletable().join(); 55 | validate(ActorConstructor, ActorCallReturning, ResultReturned, ActorDestructor); 56 | } 57 | 58 | @Test 59 | void askFuture() { 60 | assertNull(Actr.caller()); 61 | assertNull(Actr.current()); 62 | master.tell(Master::askFuture); 63 | system.shutdownCompletable().join(); 64 | validate(ActorConstructor, ActorCallReturning, ResultReturned, FutureCompleted, ActorDestructor); 65 | } 66 | 67 | @Test 68 | void askFromNonActor() { 69 | testActor.ask(TestActor::returning, val -> { 70 | assertEquals(47, val); 71 | NonActorCallback.check(); 72 | assertNull(Actr.caller()); 73 | system.shutdown(); 74 | }); 75 | system.shutdownCompletable().join(); 76 | validate(ActorConstructor, ActorCallReturning, NonActorCallback, ActorDestructor); 77 | } 78 | 79 | @Test 80 | void askFutureFromNonActor() { 81 | CompletableFuture future = testActor.ask(TestActor::returning); 82 | future.thenAccept(val -> { 83 | assertEquals(47, val); 84 | NonActorCallback.check(); 85 | assertNull(Actr.caller()); 86 | system.shutdown(); 87 | }).join(); 88 | system.shutdownCompletable().join(); 89 | validate(ActorConstructor, ActorCallReturning, NonActorCallback, ActorDestructor); 90 | } 91 | 92 | @Test 93 | void tellError() { 94 | master.tell(Master::tellError); 95 | system.shutdownCompletable().join(); 96 | validate(ActorConstructor, ActorCallThrowing, ExceptionHandler, ActorDestructor); 97 | } 98 | 99 | @Test 100 | void askError() { 101 | master.tell(Master::askError); 102 | system.shutdownCompletable().join(); 103 | validate(ActorConstructor, ActorCallThrowing, ExceptionHandler, ActorDestructor); 104 | } 105 | 106 | @Test 107 | void askErrorFuture() { 108 | master.tell(Master::askErrorFuture); 109 | system.shutdownCompletable().join(); 110 | validate(ActorConstructor, ActorCallThrowing, FutureFailed, ActorDestructor); 111 | } 112 | 113 | private class Master { 114 | 115 | void ask47() { 116 | assertNull(Actr.caller()); 117 | assertEquals(master, Actr.current()); 118 | testActor.ask(TestActor::returning, this::validateResult); 119 | assertNull(Actr.caller()); 120 | assertEquals(master, Actr.current()); 121 | } 122 | 123 | void askFuture() { 124 | CompletableFuture future = testActor.ask(TestActor::returning); 125 | assertNull(Actr.caller()); 126 | assertEquals(master, Actr.current()); 127 | 128 | future.thenAccept(value -> { 129 | validateResult(value); 130 | FutureCompleted.check(); 131 | }).exceptionally(ex -> { 132 | FutureFailed.check(); 133 | system.shutdown(); 134 | return null; 135 | }); 136 | } 137 | 138 | void askErrorFuture() { 139 | CompletableFuture future = testActor.ask(TestActor::throwing); 140 | assertNull(Actr.caller()); 141 | assertEquals(master, Actr.current()); 142 | 143 | future.thenAccept(value -> fail()).exceptionally(ex -> { 144 | assertEquals(master, Actr.current()); 145 | assertEquals(testActor, Actr.caller()); 146 | FutureFailed.check(); 147 | system.shutdown(); 148 | return null; 149 | }); 150 | } 151 | 152 | void askError() { 153 | testActor.ask(TestActor::throwing, value -> fail()); 154 | assertNull(Actr.caller()); 155 | assertEquals(master, Actr.current()); 156 | } 157 | 158 | void tellError() { 159 | testActor.tell(TestActor::throwing); 160 | assertNull(Actr.caller()); 161 | assertEquals(master, Actr.current()); 162 | } 163 | 164 | private void validateResult(int result) { 165 | assertEquals(47, result); 166 | assertEquals(master, Actr.current()); 167 | assertEquals(testActor, Actr.caller()); 168 | ResultReturned.check(); 169 | system.shutdown(); 170 | } 171 | } 172 | 173 | private class TestActor { 174 | 175 | TestActor() { 176 | assertEquals(testActor, Actr.current()); 177 | ActorConstructor.check(); 178 | } 179 | 180 | void simple() { 181 | assertEquals(testActor, Actr.current()); 182 | ActorCallSimple.check(); 183 | system.shutdown(); 184 | } 185 | 186 | int returning() { 187 | assertEquals(testActor, Actr.current()); 188 | CheckPoints.ActorCallReturning.check(); 189 | return 47; 190 | } 191 | 192 | int throwing() { 193 | CheckPoints.ActorCallThrowing.check(); 194 | throw new RuntimeException("oops"); 195 | } 196 | 197 | void err(Exception e) { 198 | assertEquals(testActor, Actr.current()); 199 | ExceptionHandler.check(); 200 | system.shutdown(); 201 | } 202 | 203 | void destructor() { 204 | assertEquals(testActor, Actr.current()); 205 | ActorDestructor.check(); 206 | } 207 | 208 | } 209 | 210 | enum CheckPoints { 211 | ActorConstructor, 212 | ActorCallSimple, 213 | ActorCallReturning, 214 | ActorCallThrowing, 215 | ActorDestructor, 216 | ResultReturned, 217 | ExceptionHandler, 218 | FutureCompleted, 219 | FutureFailed, 220 | NonActorCallback; 221 | 222 | private static List checkpoints = new ArrayList<>(); 223 | 224 | static void clean() { 225 | checkpoints.clear(); 226 | } 227 | 228 | void check() { 229 | checkpoints.add(this); 230 | } 231 | 232 | static void validate(CheckPoints... reference) { 233 | assertEquals(Arrays.asList(reference), checkpoints); 234 | } 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src/test/java/com/zakgof/actr/test/NestedAskTest.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.function.Consumer; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import com.zakgof.actr.Actr; 10 | import com.zakgof.actr.IActorRef; 11 | import com.zakgof.actr.IActorSystem; 12 | 13 | class NestedAskTest { 14 | 15 | private final IActorSystem system = Actr.newSystem("nested"); 16 | 17 | private final IActorRef master = system.actorOf(Master::new); 18 | private final IActorRef runner1 = system.actorOf(Runner1::new); 19 | private final IActorRef runner2 = system.actorOf(Runner2::new); 20 | 21 | private final IActorRef fourtySeven = system.actorOf(FourtySeven::new); 22 | 23 | @Test 24 | void testNestedAsk() { 25 | master.tell(Master::run); 26 | system.shutdownCompletable().join(); 27 | } 28 | 29 | private class Master { 30 | 31 | void run() { 32 | runner1.ask(Runner1::run, this::recv); 33 | } 34 | 35 | private void recv(int result) { 36 | assertEquals(47, result); 37 | assertEquals(master, Actr.current()); 38 | assertEquals(runner1, Actr.caller()); 39 | system.shutdown(); 40 | } 41 | } 42 | 43 | private class Runner1 { 44 | void run(Consumer callback) { 45 | assertEquals(runner1, Actr.current()); 46 | assertEquals(master, Actr.caller()); 47 | runner2.ask(Runner2::run, i -> { 48 | assertEquals(runner1, Actr.current()); 49 | assertEquals(runner2, Actr.caller()); 50 | callback.accept(i); 51 | }); 52 | } 53 | } 54 | 55 | private class Runner2 { 56 | void run(Consumer callback) { 57 | assertEquals(runner2, Actr.current()); 58 | assertEquals(runner1, Actr.caller()); 59 | fourtySeven.ask((r, c) -> c.accept(r.run()), (Integer i) -> { 60 | assertEquals(runner2, Actr.current()); 61 | assertEquals(fourtySeven, Actr.caller()); 62 | callback.accept(i); 63 | }); 64 | } 65 | } 66 | 67 | private class FourtySeven { 68 | int run() { 69 | assertEquals(fourtySeven, Actr.current()); 70 | assertEquals(runner2, Actr.caller()); 71 | return 47; 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/zakgof/actr/test/ShutdownTest.java: -------------------------------------------------------------------------------- 1 | package com.zakgof.actr.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import com.zakgof.actr.Actr; 8 | import com.zakgof.actr.IActorRef; 9 | import com.zakgof.actr.IActorScheduler; 10 | import com.zakgof.actr.IActorSystem; 11 | import com.zakgof.actr.Schedulers; 12 | 13 | class ShutdownTest { 14 | 15 | private final IActorSystem system = Actr.newSystem("massive"); 16 | private final IActorRef shutdown = system.actorOf(() -> system::shutdown); 17 | 18 | @Test 19 | void massiveShutdown() throws InterruptedException { 20 | for (int i = 0; i < 100000; i++) { 21 | IActorRef noop = system.actorOf(() -> Thread::yield); 22 | noop.tell(Runnable::run); 23 | } 24 | Thread.sleep(100); 25 | shutdown.tell(Runnable::run); 26 | system.shutdownCompletable().join(); 27 | } 28 | 29 | // @Test 30 | void shutdownDedicated() throws InterruptedException { 31 | int initialThreads = Thread.activeCount(); 32 | IActorScheduler scheduler = Schedulers.newThreadPerActorScheduler(); 33 | IActorRef d1 = system. actorBuilder().constructor(Yielder::new).scheduler(scheduler).build(); 34 | IActorRef d2 = system. actorBuilder().constructor(Yielder::new).scheduler(scheduler).build(); 35 | IActorRef d3 = system. actorBuilder().constructor(Yielder::new).scheduler(scheduler).build(); 36 | d1.tell(Runnable::run); 37 | d2.tell(Runnable::run); 38 | d3.tell(Runnable::run); 39 | 40 | Thread.sleep(100); 41 | assertEquals(initialThreads + 3, Thread.activeCount()); 42 | system.shutdown().join(); 43 | Thread.sleep(100); 44 | scheduler.close(); 45 | Thread.sleep(100); 46 | assertEquals(initialThreads, Thread.activeCount()); 47 | 48 | } 49 | 50 | private static class Yielder implements Runnable { 51 | @Override 52 | public void run() { 53 | Thread.yield(); 54 | } 55 | } 56 | 57 | } 58 | --------------------------------------------------------------------------------