Code Monkey home page Code Monkey logo

actors's Introduction

Quality Gate Status Coverage Reliability Rating Security Rating Maintainability Rating

wakatime

Actors

A simple actor based programming framework for kotlin. Currently only compatible with kotlin jvm.

Warning

This project is still experimental and lacking in features. The implementation still needs some work and refactoring, especially the KSP processor. This framework is not ready for production use, there are no official maven releases yet!

Example (Experimental)

Please note, this API is still experimental.

PersonState.kt

import com.bethibande.actors.annotations.ActorState

/**
 * An actor implementation will be generated from this class.
 */
@ActorState
data class PersonState(
    var name: String,
    var age: Int,
)

Main.kt, usage of the generated code

runBlocking {
    // Create actor-system
    val system = Person.localActorSystem()
    // Create a new actor
    val person: Person = system.new(PersonState("Max", 17))

    // Use the actor
    println("${person.getName()}: ${person.getAge()}")
    person.setAge(18)
    println("${person.getName()}: ${person.getAge()}")

    // Custom behavior/command (see com.bethibande.example.person.CustomFunctionality.kt)
    val (name, age) = person.getNameAndAge()
    println("Custom: $name, $age")

    // Send close command
    person.close()
}

CustomFunctionality.kt, adds custom functionallity / commands to the generated actor

import com.bethibande.actors.Actor
import com.bethibande.actors.behavior.Behavior
import com.bethibande.example.person.command.PersonCommand
import kotlinx.coroutines.CompletableDeferred

data class PersonCommandGetNameAndAge(
    val deferred: CompletableDeferred<Pair<String, Int>>
): PersonCommand {
    companion object: Behavior<PersonCommandGetNameAndAge, PersonState> {
        init {
            // Adds the behavior to all actors of the Person type, also affects existing actors.
            Person.BehaviorMap.add(PersonCommandGetNameAndAge::class.java, this)
        }

        override suspend fun accept(
            command: PersonCommandGetNameAndAge,
            state: PersonState,
            actor: Actor<PersonCommandGetNameAndAge, PersonState>
        ) {
            command.deferred.complete(state.name to state.age)
        }
    }
}

suspend fun Person.getNameAndAge(): Pair<String, Int> {
    val deferred = CompletableDeferred<Pair<String, Int>>()
    send(PersonCommandGetNameAndAge(deferred))
    return deferred.await()
}

Dependencies

actors's People

Contributors

bethibande avatar

Stargazers

Joshua avatar  avatar

Watchers

 avatar

actors's Issues

Add a isOpen and isClosed function to generated actor API

Task

  • Add isOpen and isClosed functions to the generated API, these functions must not send commands but simply delegate the result of the AbstractActors isOpen / isClosed function.

Motivation

A way to check if it is still possibly to interact with an actor. Sending a command to a closed actor will cause an IllegalStateException.

Notes

  • depends on #8, add these functions to the new API super-class directly

AbstractActor class not abstract anymore

Task

  • lib
    • Remove the abstract modifier of the AbstractActor class and add the open modifier
    • Rename the class to Actor
    • Simplify generics wherever possible
  • processor
    • Make the processor not generate actor classes anymore
    • Move the generated BehaviorMap field form the generated Actor class to the generated API class

Motivation

I just realized, there is no point to having an Actor class for every actor. The generated Actor classes are pretty much empty, they just implement the constructor of AbstractActor and have a companion object field which is the BehaviorMap of the actor.
If this field is moved to the API class instead, we can make the AbstractActor class non-abstract and use it as the default implementation.
This will make writing custom actors without code generation easier and further reduce the package size.

processor: rename command and behavior functions

Task

  • Rename command and behavior functions of the data classes (for example: getCommand -> commandGet; getBehavior -> behaviorGet)

Motivation

The current names for these functions may be confusing 'setCommand()', 'getCommand()' rename them to 'commandSet()', 'commandGet()'.
The same thing applies to the behavior functions 'getBehavior', 'setBehavior' and so on.
The closeCommand() function in the ActorStateType class should be renamed to commandClose() aswell.
With this the function names will also be more in line with the class names of the generated classes.

Make BehaviorMap thread-safe

BehaviorMap is currently not thread-safe, if a BehaviorMap is modifier whilst actors already use it, the map could break or even throw exception. The behavior in such a situation is unknown.
Making the class thread-safe could be challenging due to the fact that read operations occur in a coroutine context but write operations don't, also the get method used to read the map is not suspending (could be though).
Simply synchronizing the map with a ReentrantReadWriteLock is not a good idea since that could lead to coroutine worker threads getting locked.

API super-class

Task

Add a super-class (i.e interface or abstract class) for the Api classes generated by the processor.
This class should contain boilerplate code like the close function or another interface for the localActorSystem function of the companion object.
The super-class should also delegate some functions of the wrapped AbstractActor instance like isClosed and isRunning,
the send function may also become part of the super-class.

Motivation

This will reduce the boilerplate generated by the processor. Chaning this boilerplate code or adding functionallity will be easier and may not even require a change to the code-generation after this change.
Implementing this change will also make implementing custom actors without the processor easier.

ActorSystem.getById

Task

  • Add a getById to ActorSystem
  • This function will return an existing actor of the ActorSystem or null if the given id does not exist

Motivation

This function will allow to explicitly fetch an existing actor. The 'new' requires to specify a state object, this can be inconvenient if you simply wish to fetch an existing actor, why should you have to supply a state?

Notes

  • Depends on #18

Unit-Tests

Task

  • Start adding unit-tests

Motivation

Code quality & stuff

Reference counting for actors

Task

  • Implement reference counting for actors
  • Whenever an ActorSystem returns an actor, its reference count is incremented by one
  • Whenever the close function of an actor is called its reference count is decremented by one
  • If the reference count reaches 0, it's safe to assume no one is using the actor anymore and close it for good.

Motivation

As of right now it can be difficult to determine when to call an actors close function since an actor may be shared between two completely unrelated threads. If one thread calls the close function and the other thread still needs to use the actor this will cause the other thread to become unable to interact with the actor and encounter exceptions.

Reference counting will make this really easy as you can just get the actor you need and call its close function once you don't need the actor anymore. This will allow the framework to determine when it is safe to actually close the actor under the hood.

Notes

  • #31 has to be implemented before this can be worked on.

Wait for old actor to complete when re-creating

Task

  • If ActorSystem.new is invoked with an id found in the cache, check if the existing actor is closing.
  • If it is, wait for it to complete and then create the new actor, otherwise throw an exception.

Motivation

After #29, it will be possible that an actor has been closed but is still running in the background.
This renders it impossible to work with the actor.

  • The actor cannot be retrieved using ActorSyste.getById as it has been closed already (this is as it should be)
  • Creating a new actor wit the same id will throw an exception as the old one still exists in the background while it is closing.

This should be handled gracefully by simply waiting for the old actor to complete when calling ActorSystem.new with the id of an actor that is already closing.

Move shard module to a separate branch

Task

  • Create a separate branch for the shard module
  • Remove the shard module from the development branch

Motivation

The sharding module won't be usable for the foreseeable future and just gets in the way. Currently gradle cannot even build becaus of it.

processor: multi-engine support

Task

  • Add support for having multiple engines to generate source files.
  • It is not within the scope of this task to add a second engine. Provide the ability to add another engine and migrate the kotlin-poet code generation to the new system.

Motivation

I want to try and implement mustache for code-generation, however, I also do not want to remove the old kotlin-poet implementation.
The new mustache implementation will be experimental and should speed up development.
With mustache, generating docs and source code should be easier, with this it will also be possible to control the code style of the generated code. Kotlin poet does not support this.

Move versions.toml to project root

Task

  • Move the versions.toml file from ./gradle/ to the project root ./

Motivation

It's just annoying always having to open the gradle folder in order to edit the versions.toml file.

KotlinPoet formatting

KotlinPoet is currently generating code with only 2 space indents instead of 4, it also declares everything as public which is redundant. Kotlin classes, functions and properties are public by default.

DoD:

  • Change the indentation from 2 to 4 spaces.
  • Find a solution that makes KotlinPoet not add public to everything,

Filter classes properly

Filter classes annotated with @ActorState properly, it should not be possible to generate actors from object classes or interfaces for example. The processor should also throw an error if there are classes annotated with @ActorState that are not allowed.

add, remove functions

Add "add" and "remove" functions, possibly also a "contains" function to all applicable fields.

Handle Lists and MutableLists

Special handling for the MutableList class is needed as returning a MutableList will leak mutable state.

  • Instead of returning the MutableList, return an immutable copy of the MutableList
  • Add a contains command & behavior for all fields of type List.
  • Add a add, remove command & behavior for all fields of type MutableList.

Actor equals function

Task

  • Implement the equals function for all generated actor API classes
  • The equals function should compare the actor id and the backing actor instance.

Motivation

Equals comparisons are not always possible right now since the getById function for example returns a new API instance every time it is called. These instances are all using the same actor instance to send their commands to, however without an equals function it will be imposible to compare them.

Notes

  • may depend on #8, should be possible to implement the default function in the super class.

Rewrite ActorSystem, LocalActorSystem and LocalActorCache

Task

  • Rewrite the ActorSystem, LocalActorSystem and LocalActorCache to fix everything that is wrong with them.

Motivation

These classes are fine for the most part, but they are just not properly thread-safe. Some proper synchronization is needed to ensure these classes will work properly.
the LocalActorSystem.new function for example doesn't lock the id properly and if called twice with the same id from two threads it could cause the same actor to be created twice.
Creating an actor if it doesn't exist also doesn't work properly, using

if (system.exists(id)) system.getById(id) else system.new(id, state)

just isn't thread-safe.

Another issue is persisting an actor once it's completed. Currently actors already have a CompletableDeferred that can be used to wait for an actors completion however this is only used internally.
Adding a way to load and store actors to the ActorSystem should be usefull.

The LocalActorCache uses a Map which is also not synchronized, this could cause undefined behavior.

Making actor systems composabel could also be interesting.

Make generated close command wait for completion

Task

  • Turn the generated close commands into data classes instead of data objects
  • Add a deferred or something to the command which will be completed by the behavior, after the close command of the actor is invoked
  • Make the generated api function await the completion of the close commands deferred instance

Motivation

When calling the close function of a generated API object and then immediately checking if the actor is closed will not work as the command may not have been processed at that point in time

Add Sonarqube analytics

Task

  • Add SonarQube to the gradle project and github actions workflow.

Motivation

Increase stability and code quality.

File generation failing

Sometimes the source files are not generated correctly. When rebuilding gradle/intellij seems to clear the generated source files, if you didn't modify the state class before rebuilding, all generated files are now gone and won't be regenerated.
This is likely due to KSP knowing the state file was not modified since the last build thus not regenerating the files.

DoD:

  • Find a way for the files to persist ro get regenerated (preferrably persist)

Rename ActorAPI to ActorReference

Task

  • Rename ActorAPI to ActorReference

Motivation

Just makes more sense at this point, especially now that reference counting for actors is implemented, the ActorAPI classes act as a reference to the actor with some additional API functions.

chore: delete base module

Motivation

The base module was never really used and no contains no code and one, unused, proto-file which was supposed to be used for sharding.
The sharding module already has a new proto-file which does the exact same thing so there really is no need to keep this module around any longer.

processor: cleanup & generate class names before source generation

The processor needs to be cleaned up a bit. Also the class names for commands, behaviors and other generated classes should be generated before the source files are generated.
The fact that class names are generated only when the source file is generated has caused some issues when adding the actor-system, as two classes required each others class name for generation.

Rewrite close logic

Task

  • Completely reqrite how actors are closed
  • The new implementation must ensure an actor is only removed form its actor system once all messages in the actors inbox have been processed and its coroutine has completed.

Motivation

Currently it's possible for an actor to exist twice if you close the actor and then re-create it. The problem with this is that the old actor may still exist in the background, processing all commands left in it's inbox from before it was closed.
In the worst case the closed actor may even write to the database whilst a new actor already exists causing inconsistent state.

processor: Add mustache engine

Task

  • Add a new engine based on jmustache.

Motivation

This should make generating source code easier. It will also allow for the customization of the code style for generated code.

Notes

  • #35 must be completed first.

implement AutoCloseable for actor apis

Task

  • Make the generated actor api classes implement AutoCloseable
  • Should be done by making the super class (ActorAPI) implement the interface

Motivation

This should improve safety, letting the user and compiler know the actor is a resource that should be closed once it's not needed anymore

Notes

  • should not be implemented before #29 is implemented

Store actors in ActorSystem

Task

  • Store all open (non closed) actors created by an ActorSystem in said ActorSystem
  • Once an actor is closed it must be removed from the list / map
  • When creating an actor and no id is specified, it must be ensured the generated id does not yet exist

Motivation

The ActorSystem needs to store a reference to all actors it created, until they are closed.
Currently it is possible to create two actors with the same id, possibly allowing two instances of the same actor to exist at the same time.
In order to fix this the ActorSystem will need to have a list of map of all the actors it created and their ids. This will allow the ActorSystem to just return an existing instance if an actor with an existing id is created.

Automate tests

Task

  • Add a github actions runner
  • Add an action that runs the gradle tests after each push or for each commit

Motivation

Automatically running unit-tests is always good.

Move Command behavior to Command companion object

Task

  • Remove the generated Behavior classes and move the implementations into the companion object of each command. In other words make the companion object implement the Behavior interface.

Motivation

The will reduce the number of generated files.
Implementing commands this way should become the standard since it is easier and faster then having to create two classes just to add one command.

Sharding support

Support sharding actor systems across multiple shards.

  • One cluster should be able to shard multiple actor-systems at the same time.
  • Shards should be able to join on their own. In order to join the IP and Port of another running shard should be enough.
  • Networking should be handled using p2p communication and gRPC.
  • This should be implemented as a new module

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.