Skip to content

airplug/libactor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libactor

libactor is a C library implementing the Actor model using pthreads.

The library works great, but one caveat is that if one actor crashes -- they all fall down. In a future release there will be sandboxing of each actor.

To install:

./configure
make
sudo make install

Try out the example:

gcc -lactor -o example examples/example.c && ./example

You may have to run ldconfig to reload the library cache.

Usage

To use libactor, include <libactor/actor.h> in your project and compile with the flag -lactor.

Declaring your Actors

Actors are just threads, and as in pthreads, a thread is an executing function. To define a new actor you can just define a new function that takes a single void* as an argument and returns a single void*:

void * foo(void * args) {
  /* your Actor's logic goes here */
}

For clarity, you can use the macro ACTOR_FUNCTION(name, args), where name is the name of the function, and args is any arguments passed to the actor as a void*, like so:

ACTOR_FUNCTION(foo, args) {
  /* your Actor's logic goes here */
}

libactor distinguishes a main actor. This actor will be executed when the application starts, and passed a pointer to a struct containing the program arguments. In your main file, declare your main actor like so:

ACTOR_FUNCTION(main_actor, args) {
  struct actor_main * main = (struct actor_main *) args;

  /* Accessing the arguments passed to the application */
  printf("Number of arguments: %d\n", main->argc);
  for(int x = 0; x < main->argc; x++)
    printf("Argument: %s\n", main->argv[x]);
}

DECLARE_ACTOR_MAIN(main_actor)

Managing your Actors

To spawn a new Actor, use spawn_actor(function, args). To spawn the foo Actor with a NULL pointer as an argument:

ACTOR_FUNCTION(main_actor, args) {
  actor_id foo_id = spawn_actor(foo, NULL);
}

Notice the return type of spawn_actor(): an actor_id. This uniquely identifies the spawned Actor, and allows communication between Actors to identify the sender and receiver.

After a foo Actor is spawned, it can obtain its ID using actor_self():

ACTOR_FUNCTION(foo, args) {
  actor_id my_id = actor_self();
}

Message-passing

Communication uses messages. All messages are of type actor_msg_t, and are created like this:

actor_send_msg(
  foo,       /* The Actor to send to */
  PING_MSG,  /* The message type */
  NULL,      /* A pointer to the message */
  0          /* The length in bytes of the message */
); 

The type should be greater than 100, as anything below that may be used by the library.

void actor_broadcast_msg(long type, void *data, size_t size)

Broadcasts a message to all actors.

void actor_reply_msg(actor_msg_t a, long type, voiddata, size_t size)

Reply to a received message.

actor_msg_t *actor_receive()

Receives a message from the actor's mailbox.

actor_msg_t *actor_receive_timeout(long timeout)

Same as actor_receive, but let's you specify a timeout (in milliseconds).

Memory Management

libactor uses pthreads for concurrency. If you allocate memory with malloc and pass a pointer or try to access the memory in a different actor, your application may crash. Therefore, if you plan to send a message to another actor, make sure that the message is complete(no pointers, only raw data). See memory-example.

libactor provides some convenience functions for managing memory. Please use these in your actors. Reference counting is used to manage memory. When an actor exits, any unfreed memory will be automatically freed. (But you should still release anything you are not using).

void *amalloc(size_t size)

Allocates a block of memory for an actor.

void arelease(void *block)

Call this function to release the memory. The reference count is decremented. When it reaches 0, the actual memory is freed.

void aretain(void *block)

Retains a block of memory. Use this to hold on to a block of memory. The reference count is incremented.

Example

This is okay:

struct user {
  char *username;
  char *password;
  int status;
};

struct user usermsg;

// initialize usermsg here

actor_send_msg(5, 1, &usermsg, sizeof(struct user));

This is bad:

struct usr_login_info {
  char *username;
  char *password;
};

struct user {
  struct usr_login_info *info;
  int status;
};

struct user usermsg;

// initialize usermsg here

actor_send_msg(5, 1, &usermsg, sizeof(struct user));

In the bad example, the user struct will be copied, but the pointer to info may then be accessed by multiple actors.

Ping/Pong Actor Example

Below is a simple example of how to use the actor library. One actor will be spawned which will then spawn another actor, send it a ping message, and loop.

main.c:

#include <stdio.h>
#include <libactor/actor.h>

ACTOR_FUNCTION(pong_func, args) {
  actor_msg_t *msg;

  while(1) {
    msg = actor_receive();
    if(msg->type == PING_MSG) {
      printf("PING! ");
      actor_reply_msg(msg, PONG_MSG, NULL, 0);
    }
    arelease(msg);
  }
}

ACTOR_FUNCTION(ping_func, args) {
  actor_msg_t *msg;
  actor_id aid = spawn_actor(pong_func, NULL);
  while(1) {
    actor_send_msg(aid, PING_MSG, NULL, 0);
    msg = actor_receive();
    if(msg->type == PONG_MSG) printf("PONG!\n");
    arelease(msg);
    sleep(5);
  }
}


ACTOR_FUNCTION(main_func, args) {
  struct actor_main *main = (struct actor_main*)args;
  int x;

  /* Accessing the arguments passed to the application */
  printf("Number of arguments: %d\n", main->argc);
  for(x = 0; x < main->argc; x++) printf("Argument: %s\n", main->argv[x]);

  /* PING/PONG example */
  spawn_actor(ping_func, NULL);
}

Releases

No releases published

Packages

No packages published