Project developing progress:

Refactored a lot, refined some, so far actors work fine with good performance. Now I'm working on next version.

What is Psmm?

Psmm is the abbreviation for PSeudo-Mutable Message. It's a java library for actors to easily create and access immutable messages as if they were mutable ones, and with nice performance.

Messages are passed on from actors to actors, thus probably shared by threads. Akka document clearly states that messages must be immutable, otherwise Akka actors can't ensure eliminating evils of low level java concurrency. But for writing immutable object, you need to be careful about things. In this note, I wrote Psmm, so we can easily generate immutable message.

Getting started:

Maven dependency:

        <dependency>
            <groupId>com.github.cuzfrog</groupId>
            <artifactId>psmm</artifactId>
            <version>1.0.0</version>
        </dependency>

First needing to initiate PsmmSystem:

        PsmmSystem.initiate(); //only invoke once in your main thread

Creating a new message

        UMessage message = 
                Messages.builder() //get builder
                .set(key1, value1) 
                .set(key2, value2) //add some data
                ...
                .build(); 

Modifying an existed one:

        UMessage messageAfter = 
                messageBefore 
                .set(key1, value1) 
                .set(key2, value2) //add or reset some data
                ...
                .build(); 

Regressing a message:

        UMessage message3 =message2.regress();

Regression is very useful when actors work in pipeline, e.g. actor can simply check and regress a message to redo rather than resend one.

Code example:

import com.github.cuzfrog.psmm.Messages;
import com.github.cuzfrog.psmm.PsmmSystem;
import com.github.cuzfrog.psmm.UMessage;

class SimpleTest {

    @SuppressWarnings("boxing")
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        PsmmSystem.initiate(); //only invoke once in your main thread


        //create a new message:
        String key1 = "int1";
        String key2 = "int2";
        String key3 = "str1";
        UMessage message1 = 
                Messages.builder() //get builder
                .set(key1, 2)
                .set(key2, 122)
                .set(key3, "this is string") //add some data
                .build(); //without build() compile time error.
        //after passed to another actor, fetch data:
        System.out.println(message1.get(key1) 
                + "|" + message1.get(key2) 
                + "|" + message1.get(key3));
        //result: 2|122|this is string

        String key4 = "double1";
        //"modify" last message, reset some data, add some new data:
        UMessage message2 =
                message1
                .set(key2, 45)
                .set(key3, "this is another string")
                .set(key4, 568.3d)
                .build();
        //after passed to another actor, fetch data:
        System.out.println(message2.get(key1) 
                + "|" + message2.get(key2) 
                + "|" + message2.get(key3)
                + "|" + message2.get(key4));
        //result: 2|45|this is another string|568.3

        UMessage message3 =message2.regress();
        if(message3!=null){
            System.out.println(message3.get(key1) 
                    + "|" + message3.get(key2) 
                    + "|" + message3.get(key3)
                    + "|" + message3.get(key4));
        }
        //result: 2|122|this is string|null
    }
}

Using typed message (others are the same):

    TBuilder<String, Integer> b2 = Messages.typedBuilder();
    TMessage<String, Integer> message2 = b2.set("key1", 583)
                // .set("key2", "string value") //compile time error
                .build();

NOTE:When use typed message, you must ensure type's immutability.

Anti-patterns: Be careful!

[Java puzzles-56]

//now you received a message.
message.set("key1", 583).set("key2", 1773).build();  //you think you updated it.
//then send the message. ERROR, the message you sent is not the new one

//right version - must give a new reference:
UMessage newMessage=message.set("key1", 583).set("key2", 1773).build(); 
//or use the old reference:
message=message.set("key1", 583).set("key2", 1773).build(); 

Document

Introduction

Psmm messages take advantage of Decorator Pattern. Every message has a reference of preceded message. For newly created message, PsmmFactory puts a RootMessage in it. After a message has been set("modified"), factory create a new message decorating the old one, of course, with new data. Therefore, you can access psmm as if it were mutable.

For class info, please see source file.

Styles

Psmm includes different *Styles of messages standing for different structures.

A1. Linked(default): decorate old message when every setting. When getting data, first check keys in newest message, then the last one. This is the fast way to create new object, because when you create it, you don't care about the old data.But when the chain is long, it'll be slower to fetch some data. It also provides a way to regress a message: shell the outermost message, return the inner one.

A2. Flat: no decoration, read data from old message and merge them into new message.

B1. Typed: need to explicitly indicate Generic parameter Type. Thus gives flexibility to put user defined data object into message.

B2. Untyped: only takes basic immutable object, such as String, Integer etc. When retrieving data, need to cast.

C. Retained(deprecated): references of messages created are stored in a pool. Why not reuse immutable object? (But there might be a pitfall, is it really necessary to retain small object in modern JVM? Generally speaking, this is a bad idea and likely to cause other undesirable potential problems. For now I deprecated this feature. All message will be newly created.)

D1. Value: One message equals another depending on their values that they exhibit.

D2. Unique (default): No override to equals() method, message only equals itself.

The word *Style is used for distinguishing from Parameter Type.

How to choose style:

Messages.builder(Messages.Style.FLAT_MAP)

Builder and Factory model

UMessage message2 =
        message1
        .set(key2, 45)  //when first call set(), it turns to a raw message
        .set(key3, "this is another string")
        .set(key4, 568.3d)
        //.build(); //no message is really created. Type mismatch, compile time error.

A builder cannot evaluate to UMessage. Also, in concept, it's better than return a factory object.

Factory detail

There are three kinds of Modules:

  1. Creation: creates concrete message, like ValueMessage or UniqueMessage.

  2. Data: designates Data implementation into factory, like HashMap.

  3. Structure: decides which style, like Flat or Linked.

Modules work in combine, taking advantage of Decorator Pattern.

PsmmSystem and helper Messages

Configuration

Psmm provides a class PsmmConfiguration to do some custom setup when initiation. Later I shall probably provide way to customize factory Module and Data through configuration.

How to setup:

PsmmConfiguration config=new PsmmConfiguration()
            .setFactoryPoolChoseType(FactoryPoolType.NULL) //no factory pooling
            ...
PsmmSystem.initiate(config);

Performance

I've prepared some performance test data, and I'll upload them, and explain in detail.

1. creating new message and set one pair of data with factory pooled:(20 rounds warm up and tests)

Benchmark Mode Cnt Score Error Units
testCreateControlMessage avgt 20 237.161 ± 0.889 ns/op
testCreateTypedFlat avgt 20 174.108 ± 3.171 ns/op
testCreateTypedLinked avgt 20 179.215 ± 3.695 ns/op
testCreateUntypedFlat avgt 20 175.835 ± 2.523 ns/op
testCreateUntypedLinked avgt 20 172.466 ± 1.766 ns/op

Tested with jmh: 10-20 warmups, 10-20 rounds, 8 threads. Tests simple create a new message and set a pair of random key and Integer. Control message(immutable) is created via two HashMaps, one as temporary data vehicle when creating, another as data field. HW: xeon E3 1230v2, 16G DDR3 1600. OS: Windows, Oracle JDK 1.8.0_51. See source file for more detail.

It's very important to know that generating message, in most cases, only takes a fraction of application time. Sometimes, it's really not necessary to worry about their performance. When I did micro-benchmark test, yeah, disparity could be huge. However, when I did actual actor interaction test, performances are in the same scale.

Javadoc

I have written complete javadoc.

Support or Contact

Author:Cause Chung (@cuzfrog)

Any problem, please hit me on GitHub or mail me: cuzfrog@139.com / cuzfrog@gamil.com

License

The MIT License (MIT)