untu / comedy Goto Github PK
View Code? Open in Web Editor NEWNode.js actor framework.
License: Eclipse Public License 1.0
Node.js actor framework.
License: Eclipse Public License 1.0
Is it normal practice to use this framework with firebase or aws ec2? I wasn't find any practice about it
Originally posted by @vziatkov in #28 (comment)
Here's what I'm trying to achieve:
My requirement is: command belonging to a single aggregate must be handled sequentially, although command belonging to different aggregates can be running in parallel.
Is it possible to setup the cluster of actors in such a way that when a command of an aggregate is being handled, other commands belonging to same aggregate will be queued until previous command is completed; while if command belonging to other aggregate comes in then it will be handled?
I'm envisioning solution like this:
But this may have problem of single actor being more loaded if aggregateId belonging to bucket of that particular actor comes in frequently
Users may want to define custom balancer for clustered actors, where they can implement affinity routing.
Implement an actor mailbox size limit capability. This way, a user will be able to avoid overloading actors with too many messages.
When i use forked actors, i got this error (and don't know why and where)
Error occurring at /var/www/scripts/node_modules/comedy/lib/forked-actor.js:476:18
Full error:
TypeError: Cannot read property 'send' of undefined at /var/www/scripts/node_modules/comedy/lib/forked-actor.js:476:18 at Promise._execute (/var/www/scripts/node_modules/comedy/node_modules/bluebird/js/release/debuggability.js:299:9) at Promise._resolveFromExecutor (/var/www/scripts/node_modules/comedy/node_modules/bluebird/js/release/promise.js:481:18) at new Promise (/var/www/scripts/node_modules/comedy/node_modules/bluebird/js/release/promise.js:77:14) at ForkedActorParent._send0 (/var/www/scripts/node_modules/comedy/lib/forked-actor.js:421:12) at ForkedActorParent.transmitBusMessage (/var/www/scripts/node_modules/comedy/lib/forked-actor.js:108:10) at /var/www/scripts/node_modules/comedy/lib/system-bus.js:82:19 at Set.forEach (<anonymous>) at SystemBus._broadcastToForkedRecipients (/var/www/scripts/node_modules/comedy/lib/system-bus.js:80:21) at SystemBus.emitFromActor (/var/www/scripts/node_modules/comedy/lib/system-bus.js:44:10) at ChildProcess.<anonymous> (/var/www/scripts/node_modules/comedy/lib/forked-actor.js:274:25) at ChildProcess.emit (events.js:314:20) at emit (internal/child_process.js:906:12) at processTicksAndRejections (internal/process/task_queues.js:81:21)
My architicture:
Forked actor running children forked actors (4), and each child running own one threaded and one forked actors. I am using bus for messaging between them.
My solution:
If i change this chain Forked -> Forked -> Forked to this Forked -> Forked -> Threaded it will be ok.
But i need only Forked -> Forked -> Forked
when running the basic getting started example the following error appears:
(node:290995) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
followed by a whole trace if you add the --trace-warnings
flag.
This was fixed in Winston 2.4.5 but they do recommend moving to Winston 3.
That said, shipping with a logger is crossing concerns...
Libraries shouldn't have baked in loggers, but they can have interface to configure your own logger. for example you can pass a log function handler
import comedy from 'comedy';
comedy.setLogger((category, level, ...msg) => { /*handle however I want*/ });
// or
comedy.setLoggerFactory((category) => (level, ...msg) => { /* create a logger for each category*/ });
// then the rest of the usage stays exactly the same.
I'm getting an error when I try to reference external symbols in forked agents.
Am I doing something wrong?
const actors = require('comedy');
function externalFunction() {
console.log("CIAO");
}
/**
* Actor definition class.
*/
let MyActor = {
sayHello: function () {
externalFunction();
}
};
async function bootstrap() {
let actorSystem = actors();
try {
let root = await actorSystem.rootActor();
console.log("== Creating in memory actor ==");
await (await root.createChild(MyActor, {mode: 'in-memory'})).sendAndReceive(`sayHello`); // ok
console.log("== Creating forked actor ==");
await (await root.createChild(MyActor, {mode: 'forked'})).sendAndReceive(`sayHello`); // not ok
} catch (e) {
console.log("error: ", e);
await actorSystem.destroy();
}
}
bootstrap();
== Creating in memory actor ==
CIAO
== Creating forked actor ==
CIAO
Tue Jun 11 2019 09:42:40 GMT+0200 (GMT+02:00) - info: Didn't find (or couldn't load) default configuration file C:\comedy-sandbox/actors.json.
Tue Jun 11 2019 09:42:40 GMT+0200 (GMT+02:00) - info: Resulting actor configuration: {}
== Creating in memory actor ==
CIAO
== Creating forked actor ==
error: Error: externalFunction is not defined
at ChildProcess.bus.on (C:\comedy-sandbox\node_modules\comedy\lib\forked-actor.js:224:32)
at ChildProcess.emit (events.js:189:13)
at emit (internal/child_process.js:820:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
Tue Jun 11 2019 09:42:41 GMT+0200 (GMT+02:00) - info: Killing forked system process.
Tue Jun 11 2019 09:42:41 GMT+0200 (GMT+02:00) - info: ForkedActorParent(5cff5b70806f34235cfa76d1, Object): Actor process exited, actor ForkedActorParent(5cff5b70806f34235cfa76d1, Object)
OS: Windows 10
node --version: v10.15.3
npm --version: 6.9.0
npm info comedy version: 2.1.0
For now Comedy uses simple point-to-point channels for message exchange. There should be an option to use Kafka message broker as an underlying transport for more advanced and robust messaging.
This is to ensure we always have proper type definitions and to avoid bugs like #24 .
For development and debugging purposes it may be useful to transfer logs from remote actor to a parent actor to see the full picture of an actor system in the root node log.
In frameworks like Akka, the concept of Location Transparency is always stressed a lot. It allows moving actors to another node, which in turn allows for dynamically growing and shrinking a cluster. Also live software updating of framework or application would require this.
Does the comedy team envision implementing something like this? Or is comedy built on a different vision that does not require these concepts?
Getting comedy to work in the browser would be an interesting development.
removing dependencies on node and also converting the project to use ES modules.
[I've been working a couple of days with your library and I wanted to thanks you because I really enjoy it.]
Coming back to my question, how can I initialize an actor with some parameters ?
When an actor is forked, it doesn't have access to the scope.
When launching remote actor for testing purposes, it's often convenient to specify SSH credentials rather than deploying comedy-node on remote machine by hand. Comedy should support this.
Users should be able to change actor configuration in actors.json
without application restart. The configuration should be applied on-the-fly.
Is there any support for being able to assign identifiers to actors upon creation such that they could be addressed generically? All the examples I've seen so far seem to rely on having an actual reference to the actor in memory which makes it harder to support messaging between different actors.
How can I know if an actor has message in its mailbox that should be processed ?
Shouldn't toobusy-js
be a normal dependency as it's used here https://github.com/untu/comedy/blob/master/lib/actor-system.js#L40 ? I had to manually install it
Since all messages are going through the root actor, he may as well achieve some work. Can I define messages and associated function for the root actor ?
See this issue for more context.
I just found out about this project, and I find the scaling capabilities particularly interesting.
Just out of curiosity, have you considered the possibility of scaling out to other machines rather than just other processes?
It would be really interesting it we were able to configure an entire cluster, allocating actors to different machines, but using the same interface to communicate between them.
What would be the challenges in making something like this happen?
This is probably the least important issue but figured we can discuss it anyway.
All the current stable versions of node and browsers support promises and bluebird is not a dependency that libraries should ship with if it can be avoided.
the codebase itself could really benefit from cleanup and a switch to native promises and async/await
.
I don't see this adding much value other than decreasing the likelihood of silly errors like #62 and general code legibility (promises with callbacks tend to be less legible in most circumstances than the equivalent async/await).
P.S. I am aware that not ALL promise usages should be transferred to async/await, especially for concurrency but most of the functions tend to be step by step inside anyway.
For now, Comedy only supports round-robin balancing. We should also add random balancer as an option (configured via balancer
actor parameter).
Hello,
thanks for this awesome library.
I would like to know how to share an object between forked actors.
Here is an example:
var pile = []
class Player {
count(to) {
console.log(pile.length);
}
}
class Croupier {
add(to) {
pile.push(9);
}
}
var actorSystem = actors();
actorSystem
.rootActor()
.then(function(rootActor) {
Promise.all([rootActor.createChild(Player, {mode: 'forked', clusterSize: 3}), rootActor.createChild(Croupier)])
.then(children => {
var player = children[0];
var croupier = children[1];
setInterval(() => player.send('count'), 10);
setInterval(() => croupier.send('add'), 1000)
})
})
This example works when Player actors are not forked but as soon as they are, they can't access the list pile
anymore. Player should only be able to read the object, only Croupier
car modify the object.
The only solution I see is that each Player
gets a copy of pile
in a message, but I'm scared it will have big memory footprint.
Any help appreciated.
Hi guys,
I'm trying to run an actor as forked mode and this actor class is compiled with babel, but when runs the actor I got the following error:
ReferenceError: _classCallCheck is not defined
What am I missing?
Thanks,
When I run tsc, the compiler tells:
xxx/AppData/Roaming/npm/node_modules/typescript/lib/lib.es2015.iterable.d.ts:41:6 - error TS2300: Duplicate identifier 'IteratorResult'.
41 type IteratorResult<T, TReturn = any> = IteratorYieldResult | IteratorReturnResult;
~~~~~~~~~~~~~~node_modules/@types/node/index.d.ts:165:11
165 interface IteratorResult { }
~~~~~~~~~~~~~~
'IteratorResult' was also declared here.
And my tsconfig.json is:
{
"exclude": [
"node_modules/@types/node/index.d.ts"
],
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es6"],
"strict": true,
"esModuleInterop": true
}
}
Typescript version is 3.7.2
The "exclude" in tsconfig.json makes no difference.
Next dependencies better to move from "dependencies" to "devDependencies"
"@types/chai": "4.1.4",
"@types/mocha": "5.2.2",
As a result of "required" dependencies, it can create conflicts with other test frameworks used in the project who consumes "comedy".
If no, can we make it happen?
This thread is about the discussion around the topic so it should stay open for a while.
The idea is that the library shouldn't actually be doing the configuration because it violates the primary concerns of comedy.
As most advanced use cases would just use their own config loading.
However, hot-loading is very useful.
So I see a number of options:
const CONFIG_FILES = ['file1.json', 'file2.yaml']
comedy.setConfigLoader(CONFIG_FILES, (changedfile) => {
// custom parsing and handling
// then return the updated config in the JSON format currently read directly from files
return {};
})
import hotloader from `@comedy/hotloader`
hotloader.watch('file1.json', () => {
// callback fires on config changes
// custom parsing and handling
const config = {};
// then return the updated config as a JSON object in the format currently read directly from files
comedy.updateConfig(config);
}
hotloader.watch('file2.yaml', () => {
// callback fires on config changes
// custom parsing and handling
const config = {};
// then call update with updated config as a JSON object in the format currently read directly from files
comedy.updateConfig(config);
}
there are likely other options, the biggest issue is backwards compatibility,
I lean towards option 2 because it keeps comedy focussed on what it should, which is the actor system, and you can
keep BC if you detect that the @comedy/hotloader
module is installed and the config
option is set as a path to a json file and not a json object (which it should support as well)
Is there an easy way to switch out the logger? The application I'm porting over is using bunyan and I'd prefer to not have to redo it through out.
When my actor system using Comedy runs it says on the console.
Mon Sep 10 2018 19:28:17 GMT+0800 (China Standard Time) - info: Loaded default actor configuration file: C:\home\projects\ContractPen\contractpen_node_client/actors.json
Mon Sep 10 2018 19:28:17 GMT+0800 (China Standard Time) - info: Resulting actor configuration: {}
How to prevent it from saying this upon startup on console? I don't want any output to console to occur.
I guess the hot reloading code is just missing this behavior entirely?
Great framework! Is there any ideas what will be implemented later?
It could be something like https://www.testcontainers.org/ROADMAP.md
It's not an issue just a suggestion.
From the looks of this config
Line 6 in e523ff8
The two pr's #75 and #74 are blocked because a configuration to install the latest version of npm, and the latest npm doesn't run with the node 10 version in the CI checks
from pr 75's checks https://ci.appveyor.com/project/weekens/comedy/builds/43166798
ERROR: npm is known not to run on Node.js v10.24.1
The last time this seemed to pass was in PR #73 and it ran with npm version 7.20.5
Can I create a PR to change the line above to npm i [email protected] -g
Currently ActorRefs can be passed to Actor calls, but when using forked or threaded actors, this only is correctly handled for a few cases:
Nesting is not supported at all, so passing a method parameter of types {a: ActorRef, b: ActorRef} fails.
Additionally you can also not return ActorRefs from actor methods.
There are workaround, but they are cumbersome.
I think bus messages also cannot transport ActorRefs.
It would make some scenarios easier to handle to add handling in more cases.
Implement automatic actor scaling up and down according to the load.
It would be great to set different config for development
test
production
. What do you think about this?
Are there any plans to support supervision like [Nact](https://nact.io/lesson/javascript/supervision](https://nact.io/lesson/javascript/supervision) and others do?
When a message is sent through a balancer actor to a cluster actor that crashes, the balancer should attempt to re-route that message to another actor in the cluster. The number of failover attempts should be configurable.
e.g. no getBus()
method in ActorSystem
.
Example:
const rt = await system.rootActor();
// local actor L
const localPrinter = await rt.createChild({
pipe(data: any) {
console.log("I got data piped!", data);
}
});
// forked actor F
const forkedActor = await rt.createChild("/dist/src/dbg", {
mode: "forked"
});
/**
giveRef(ref: ActorRef) {
setInterval(() => {
console.log("The actor state of my ref is: " + ref.getState());
if (ref.getState() === "ready") {
ref.send("pipe", Date.now());
}
}, 1000);
}
*/
forkedActor.send("giveRef", localPrinter);
/**
* Prints:
* The actor state of my ref is: ready
I got data piped! 1607799890757
The actor state of my ref is: ready
I got data piped! 1607799891754
The actor state of my ref is: ready
I got data piped! 1607799892754
The actor state of my ref is: ready
I got data piped! 1607799893755
The actor state of my ref is: ready
*/
await new Promise<void>(resolve => {
setTimeout(() => {
resolve();
}, 5000);
});
// destroy local actor
await localPrinter.destroy();
// now the prints continue:
/**
The actor state of my ref is: ready
Sat Dec 12 2020 20:04:55 GMT+0100 (Central European Standard Time) - warn: ForkedActor(5fd51451843499b4f6d3a668): No pending promise for "actor-response":
{
type: 'actor-response',
id: 6,
body: {
error: 'destroy() has been called for this actor, no further interaction possible'
},
actorId: '5fd51451843499b4f6d3a668'
}
*/
await new Promise<void>(resolve => {
setTimeout(() => {
resolve();
}, 5000);
});
It should probably not return "ready" for getState() on ref in the forked actor, after the referenced actor has been destroyed.
If I use sendAndReceive("pipe", ...) instead of just send, that one throws an exception saying the actor has been destroyed, but the state of the actor in the forked actor still reports "ready".
send on the other hand just fails silently and causes those warning prints to show up a lot.
Since the root actor creates all the children sequentially, I don't see how different kind of children can exchange messages without passing by root.
For example, I need my Player
actor to send data to Croupier
.
The two choices I see are:
class Player {
constructor() {
this.currentPile = []
this.master = null
}
start(pile) {
this.currentPile = pile;
this.currentPile.forEach((x) => this.master.send('add', x))
}
setMaster(master) {
this.master = master
}
}
class Croupier {
constructor() {
this.pile = []
}
add(value) {
this.pile.push(value);
}
print(to) {
console.log(this.pile);
}
}
var actorSystem = actors();
actorSystem
.rootActor() // Get a root actor reference.
.then(function(rootActor) {
Promise.all([rootActor.createChild(Player, {mode: 'forked', clusterSize: 3}), rootActor.createChild(Croupier)])
.then(children => {
var player = children[0];
var croupier = children[1];
player.send('setMaster', croupier);
player.send('start', [1,2,3]);
setInterval(() => croupier.send('print'), 1000)
})
})
Anyhow, when running the previous code, there is an error (again only when forked):
Unhandled rejection TypeError: Converting circular structure to JSON
at Object.stringify (native)
at ChildProcess.target._send (internal/child_process.js:626:23)
at ChildProcess.target.send (internal/child_process.js:538:19)
at P (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/forked-actor.js:322:16)
at Promise._execute (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/debuggability.js:299:9)
at Promise._resolveFromExecutor (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/promise.js:481:18)
at new Promise (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/promise.js:77:14)
at ForkedActorParent._send0 (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/forked-actor.js:298:12)
at ForkedActorParent._sendActorMessage (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/forked-actor.js:263:17)
at ForkedActorParent.send0 (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/forked-actor.js:183:17)
at ForkedActorParent.send (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/actor.js:161:19)
at ForkedActorStub.send (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/forked-actor-stub.js:70:23)
at currentChildPromise.then.child (/Users/vallettea/Desktop/actors/node_modules/comedy/lib/standard/round-robin-balancer-actor.js:119:64)
at tryCatcher (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/promise.js:510:31)
at Promise._settlePromise (/Users/vallettea/Desktop/actors/node_modules/bluebird/js/release/promise.js:567:18)
What I am doing wrong ?
See test.
Branch socket-respawn-after-crash
Hello!
I'm starting a new project with TypeScript and currently experimenting with Comedy as a solution for scaling the application.
I ran into an issue with the way Resources are serialized and sent to the forked process which is causing a lot of headaches and seems like a big limitation (if I'm understanding this correctly).
The original Resource definition is as follows:
import { ActorSystem, ResourceDefinition } from 'comedy';
import { getResource } from '../helpers/test';
import { ComedyResource } from '../decorators/ComedyResource';
@ComedyResource('TestResource')
export default class TestResource implements ResourceDefinition<string> {
destroy(): Promise<void> | void {
// nothing to do here
return undefined;
}
getResource(): string {
// return from an imported function as a test
return getResource();
}
initialize(system: ActorSystem): Promise<void> | void {
// nothing to do here
return undefined;
}
}
But what is sent to the child process (in the create-actor
message) is:
class TestResource {
destroy() {
// nothing to do here
return undefined;
}
getResource() {
// return from an imported function as a test
return test_1.getResource();
}
initialize(system) {
// nothing to do here
return undefined;
}
}; TestResource;
As you can see, the imports are all missing and there is no way this can work.
@Zephyrrus noticed that you can use require()
inside the definition and to import things, but they are imported from the wrong working directory and therefore don't resolve properly.
As a workaround I considered creating dummy "shells" that would dynamically load the correct file (from disk) with require(), but that sounds very cumbersome to maintain.
Is there any solution to this? Am I missing something?
// the referenced actor file:
export default class Printer {
printNumber(n: number) {
console.log(n);
}
}
// main file:
const root = await system.rootActor();
async function doTest(mode: "in-memory" | "forked" | "threaded") {
const testActor = await root.createChild("/dist/src/printer", {
mode
});
console.log("Output for: " + mode);
await testActor.sendAndReceive("printNumber", 0);
await testActor.destroy();
}
await doTest("in-memory");
await doTest("forked");
await doTest("threaded");
This prints:
Output for: in-memory
0
Output for: forked
undefined
Output for: threaded
undefined
I'd expect it to print 0 for all 3 cases, it seems there must be some sort of if (message) ... in the code somewhere that causes this.
How can I ensure that only one actor is created in whole actor system cluster? Also somewhat relates to #17
Problem:
While running an app based on comedy framework we're getting an error:
Warning: Promise.defer is deprecated and will be removed in a future version. Use new Promise instead.
at P (/home/node/work/node_modules/comedy/lib/forked-actor.js:433:25)
From previous event:
at ForkedActorChild._send0 (/home/node/work/node_modules/comedy/lib/forked-actor.js:421:12)
at ForkedActorChild._pingParent (/home/node/work/node_modules/comedy/lib/forked-actor-child.js:105:17)
at Timeout.setInterval [as _onTimeout] (/home/node/work/node_modules/comedy/lib/forked-actor-child.js:44:16)
at ontimeout (timers.js:436:11)
at tryOnTimeout (timers.js:300:5)
at listOnTimeout (timers.js:263:5)
at Timer.processTimers (timers.js:223:10)
Source:
/lib/forked-actor.js
, method _send0
(https://github.com/untu/comedy/blob/master/lib/forked-actor.js#L433)
I think that the main issue here is how do you use Bluebird library. According to https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern you should not use defer
and pending
methods on P
object.
Expected behavior:
No warning in a console.
It seems the actor state names are not documented.
They appear to be "new", "ready" and maybe a few more such as "destroying" or "destroyed".
And "crashed"?
They should be documented.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.