Which works fine, as long as I'm only using one thread. Multiple Actors, of course, see different SingletonMap instances (!) since Actor.locals["map"] is per-thread storage.
Is there any way to implement a Singleton which is the same singleton for all threads?
Thanks!
vkuzkokovSat 28 Aug 2010
We shall dispatch a message every time we want to access map. Messages to the same actor are guaranteed to be processed within one Actor's context, hence the name.
using concurrent
const class SingletonMap : Actor
{
static SingletonMap getInstance()
{
return instance
}
private static [Str:Int] getMap()
{
if (Actor.locals["map"] == null)
{
Actor.locals["map"] = [Str:Int][:]
}
return Actor.locals["map"]
}
override protected Obj? receive(Obj? msg) {
if (msg == null)
{
Actor.locals["map"] = null
}
if (msg is Str)
{
return getMap.get(msg)
}
if (msg is Obj[])
{
Obj[] ent := msg
return getMap.set(ent[0], ent[1])
}
return null
}
Int? getValueForKey(Str key)
{
return send(key).get()
}
Void setValueForKey(Str key, Int val)
{
send([key,val])
}
Void resetForTestingOnly()
{
send(null)
}
private new make(ActorPool p) : super(p)
{
}
private static const SingletonMap instance := SingletonMap(ActorPool())
}
yachrisSat 28 Aug 2010
Hey vkuzkokov,
Thanks very much for the fix! Much appreciated. Good insight, too...
vkuzkokovSat 28 Aug 2010
Here's solution I wrote first. It's little bit harder to comprehend. Though it's shorter and somewhat more elegant.
What happens here is that we send a function which is executed in Actor's context. So if we had:
doSmth
We make it:
return send(|->Obj?|
{
doSmth
}).get()
Also we omit return and get if we don't want to return.
brianSat 28 Aug 2010
Here is what I would probably code as a basic singleton map example:
const class SingletonMap
{
const static SingletonMap val := make()
Obj? get(Str name) { actor.send(name).get(200ms) }
Void set(Str name, Obj? val) { actor.send([name, val]) }
private new make()
{
actor = Actor(ActorPool()) |msg| { receive(msg) }
}
private Obj? receive(Obj? msg)
{
Str:Obj? map := Actor.locals.getOrAdd("map") { Str:Obj[:] }
if (msg is Str)
return map[msg]
else
return map.set(msg->get(0), msg->get(1))
}
private const Actor actor
}
Couple comments:
I usually make the actor a private field instead of subclassing from Actor so that I don't expose that to the public API
I typically always include a timeout in my blocking sends to throw a TimeoutErr just in case something goes wrong
yachrisSat 2 Oct 2010
One minor change I made today:
const class SingletonMap
{
const static SingletonMap val := make()
Obj? get(Str name) { actor.send(name).get(200ms) }
Void set(Str name, Obj? val) { actor.send([name, val].toImmutable) } // note .toImmutable
private new make()
{
actor = Actor(ActorPool()) |msg| { receive(msg) }
}
private Obj? receive(Obj? msg)
{
Str:Obj? map := Actor.locals.getOrAdd("map") { Str:Obj[:] }
if (msg is Str)
return map[msg]
else
return map.set(msg->get(0), msg->get(1))
}
private const Actor actor
}
The beauty of this is that, if you've got const objects you're storing, they (A) don't have to be @Serializable and (B) you'll get the exact same (i.e., ===) object out that you got in, for a specific key.
yachris Sat 28 Aug 2010
Hello,
Trying to do a singleton map:
Which works fine, as long as I'm only using one thread. Multiple Actors, of course, see different SingletonMap instances (!) since
Actor.locals["map"]
is per-thread storage.Is there any way to implement a Singleton which is the same singleton for all threads?
Thanks!
vkuzkokov Sat 28 Aug 2010
We shall dispatch a message every time we want to access map. Messages to the same actor are guaranteed to be processed within one Actor's context, hence the name.
yachris Sat 28 Aug 2010
Hey vkuzkokov,
Thanks very much for the fix! Much appreciated. Good insight, too...
vkuzkokov Sat 28 Aug 2010
Here's solution I wrote first. It's little bit harder to comprehend. Though it's shorter and somewhat more elegant.
What happens here is that we send a function which is executed in Actor's context. So if we had:
We make it:
Also we omit return and get if we don't want to return.
brian Sat 28 Aug 2010
Here is what I would probably code as a basic singleton map example:
Couple comments:
yachris Sat 2 Oct 2010
One minor change I made today:
The beauty of this is that, if you've got const objects you're storing, they (A) don't have to be
@Serializable
and (B) you'll get the exact same (i.e., ===) object out that you got in, for a specific key.