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:
using concurrent const class SingletonMap : Actor { static SingletonMap getInstance() { return instance } Int? getValueForKey(Str key) { [Str:Int]? map := Actor.locals["map"] if (map == null) { map = [:] Actor.locals["map"] = map } return map[key] } Void setValueForKey(Str key, Int val) { [Str:Int]? map := Actor.locals["map"] if (map == null) { map = [:] Actor.locals["map"] = map } map[key] = val } Void resetForTestingOnly() { Actor.locals["map"] = null } private new make(ActorPool p) : super(p) { } private static const SingletonMap instance := SingletonMap(ActorPool()) }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.
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()) }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.
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 is |->Obj?|) { |->Obj?| proc := msg return proc() } Actor.locals["map"] = null return null } Int? getValueForKey(Str key) { return send(|->Obj?|{ return getMap.get(key) }).get() } Void setValueForKey(Str key, Int val) { send(|->Obj?|{ getMap.set(key, val) }) } Void resetForTestingOnly() { send(null) } private new make(ActorPool p) : super(p) { } private static const SingletonMap instance := SingletonMap(ActorPool()) }What happens here is that we send a function which is executed in Actor's context. So if we had:
We make it:
return send(|->Obj?| { doSmth }).get()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:
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:
yachris Sat 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
@Serializableand (B) you'll get the exact same (i.e., ===) object out that you got in, for a specific key.