#1097 Singleton pattern implementaion

pavel_repin Fri 21 May 2010

I can't find answer on this question. How possible implement singleton pattern?

I try to use Atomic reference. But reference don't work also:

class WServer
{
	private static const AtomicRef instance_ := AtomicRef()
	static WServer instance()
	{
		return instance_.val
	}
	static Void init()
	{
		instance_.compareAndSet( null, WServer() )
	}
}

But I can implement singleton Sequence:

class WSequence
{
	static const AtomicInt currentValue := AtomicInt()
	static Int next()
	{
		return currentValue.getAndIncrement()
	}
}

This is too strange for me.

brian Sun 23 May 2010

No need to do anything complicated like use AtomicRef. You can use the same sort of design you might use in Java:

const class Foo
{
  const static Foo instance := make()
  private new make() { ... }
}

However since a singleton is by definition shared between all threads, then it must be immutable to guarantee thread safety (as all static fields must be).

If you singleton is not immutable, then you must make it an actor.

pavel_repin Sun 23 May 2010

Ok. I done as you say.

I try to develop web application in maner desktop gui application. In my program for each client session I create gui application that can rendered into html. User can iteract with application that associated with his session and change application's state.

I need not immutable singleton and make an Actor in two versions.

In first version I store applications in an Actor and return application for current session in Future as serializable object. This is bad solution because application encoded and decoded twice on each request. First time when request get current application and second time after application handle request and her state was changed.

const class WServer : Actor
{
  private static const Str appFactoryKey := "WServer.appFactory"
  private static const Str serviceKey := "WServer.service"
  
  static const WServer instance := WServer()
  private new make()
    : super( ActorPool() )
  {
  }

  protected override Obj? receive(Obj? msg)
  {
    if( msg == null )
    {
      WLog.err( "WServer received null message" )
      return null
    }
    
    if( msg is Str )
    {
      Str sessionID := msg
      WLog.info( "Received request from session: $sessionID" )
      WApp? app := locals.get( sessionID )
      if( app == null )
      {
        WAppFactory? appFactory := locals.get( appFactoryKey )
        if( appFactory == null )
        {
          WLog.err( "AppFactory not initialized" )
          return null
        }
        
        app = appFactory.create
        app.setSessionID( sessionID )
        locals.set( sessionID, app )
        WLog.info( "Application ${app.typeof.name} created for session: $sessionID" )
      }
      
      return app
    }

    if( msg is WApp )
    {
      WApp app := msg
      locals.set( app.sessionID, app )
      return null
    }

    if( msg is InitParams )
    {
      InitParams initParams := msg

      locals.set( appFactoryKey, initParams.appFactory )
  
      WispService service := WispService
      {
        it.port = initParams.port
        it.root = WWebMod()
      }
      locals.set( serviceKey, service )
      
      return service
    }

    WLog.err( "WServer received incorrect message: ${msg.typeof.name}" )
    return null
  }
}

@Serializable
class InitParams
{
  WAppFactory? appFactory
  Int? port
}

internal const class WWebMod : WebMod
{
  override Void onGet()
  {
    handleRequest
  }
  override Void onPost()
  {
    handleRequest
  }
  
  private Void handleRequest()
  {
    Future future := WServer.instance.send( req.session.id )
    
    WApp? app := future.get
    if( app == null )
    {
      WLog.err( "WServer response null app" )
      return
    }
    
    app.handleRequest( req, res )
    WServer.instance.send( app )
  }
}

In second version I store applications in an Actor also. But I handle request in this Actor thread. This solution solve problems with serialization but all request will be processed in one thread. That's bad.

const class WServer : Actor
{
  private static const Str appFactoryKey := "WServer.appFactory"
  private static const Str serviceKey := "WServer.service"
  
  static const WServer instance := WServer()
  private new make()
    : super( ActorPool() )
  {
  }

  protected override Obj? receive(Obj? msg)
  {
    if( msg == null )
    {
      WLog.err( "WServer received null message" )
      return null
    }
    
    if( msg is WReq )
    {
      WReq req := msg
      Str sessionID := req.sessionID
      WLog.info( "Received request from session: $sessionID" )
      WApp? app := locals.get( sessionID )
      if( app == null )
      {
        WAppFactory? appFactory := locals.get( appFactoryKey )
        if( appFactory == null )
        {
          WLog.err( "AppFactory not initialized" )
          return null
        }
        
        app = appFactory.create
        app.setSessionID( sessionID )
        locals.set( sessionID, app )
        WLog.info( "Application ${app.typeof.name} created for session: $sessionID" )
      }
      
      return app.handleRequest( req )
    }

    if( msg is InitParams )
    {
      InitParams initParams := msg

      locals.set( appFactoryKey, initParams.appFactory )
  
      WispService service := WispService
      {
        it.port = initParams.port
        it.root = WWebMod()
      }
      locals.set( serviceKey, service )
      
      return service
    }

    WLog.err( "WServer received incorrect message: ${msg.typeof.name}" )
    return null
  }
}

@Serializable
class InitParams
{
  WAppFactory? appFactory
  Int? port
}

internal const class WWebMod : WebMod
{
  override Void onGet()
  {
    handleRequest
  }
  override Void onPost()
  {
    handleRequest
  }
  
  private Void handleRequest()
  {
    WReq wreq := WReq( req.session.id, req.form )
    Future future := WServer.instance.send( wreq.toImmutable )
    
    WRes? wres := future.get
    if( wres == null )
    {
      WLog.err( "WServer response null" )
      return
    }

    res.headers["Content-Type"] = "text/html; charset=utf-8"
    res.headers["Title"] = wres.title
    res.out.print( wres.out )
  }
}

Can you help me to improve this program?

brian Sun 23 May 2010

In general you won't wrap up WispService inside another actor. Rather your server's boot script will just launch one or more configured services. For example:

// launch server
MyAppService { }.start
WispService { it.port = port }.start

In this case instead of a singleton, you probably want to create your application as a service (or maybe just a WebMod). Then you can lookup:

MyAppService service := Service.find(MyAppService#)

Your service would probably use actors under the covers. If you can try to make your actor messages const which makes them super efficient. But if you have to serialize your messages it should still be pretty performant.

Maybe you can explain your application a bit more at the high level.

pavel_repin Sun 23 May 2010

Thank you for your interest and help, Brian

My project have two pods: hello.pod - demo hello world app and webtoolkit.pod - web library similar to http://www.webtoolkit.eu/wt but very simplify.

hello.pod classes:

using util
using webtoolkit

class Hello : AbstractMain
{
  @Opt { help = "port" }
  Int port := 8080

  override Int run()
  {
    WServer server := WServer( HelloAppFactory(), port )
    runServices( [ server.service ] )
    return 0
  }
}
using webtoolkit

@Serializable
class HelloAppFactory : WAppFactory
{
  override WApp create()
  {
    return HelloApp()
  }
}
using webtoolkit

class HelloApp : WApp
{
  private WLineEdit nameEdit
  private WText greetingText
  private WTable friendsTable
	
  new make()
  {
    this.title = "Hello world"                           // application title
		
    this.root.addWidget( WText("Input friend name, please ? ") ) // show some text
    this.nameEdit = WLineEdit( "", this.root )           // allow text input
		
    WPushButton b := WPushButton( "Great friend", root ) // create a button
    b.setMargin( [10:WSide.left] )                       // add 10 pixels margin
		
    this.root.addWidget( WBreak() )                      // insert a line break
		
    this.greetingText = WText( "", root )                // empty text
    this.root.addWidget( WHeader("Greated friend's list") ) // show some header
    this.friendsTable = WTable( root )                   // empty  table
    this.friendsTable.setHeaders( [ WText("Friend's name") ] )
		
    |->| greating := |->|
    {
      Str name := this.nameEdit.text.trim
      if( name.isEmpty )
        this.greetingText.text = "You don't input friend's name"
      else
      {
        this.greetingText.text = "Hello there, " + name
        this.friendsTable.addRow( [ WText( name ) ] )
      }
    }
    b.clicked = greating
    nameEdit.enterPressed = greating
  }
}

I think that this code explain more.

Webtoolkit.pod main classes:

const class WServer
{
  const WispService service
  
  new make( WAppFactory appFactory, Int port := 8080 )
  {
    WRequestHandler.instance.send( appFactory )

    this.service = WispService()
    {
      it.port = port
      it.root = WWebMod()
    }
  }
}
internal const class WWebMod : WebMod
{
  override Void onGet()
  {
    handleRequest
  }
  override Void onPost()
  {
    handleRequest
  }
  
  private Void handleRequest()
  {
    WReq wreq := WReq( req.session.id, req.form )
    Future future := WRequestHandler.instance.send( wreq.toImmutable )
    
    Str? resHtml := future.get
    if( resHtml == null )
    {
      WLog.err( "WServer response null" )
      return
    }

    res.headers["Content-Type"] = "text/html; charset=utf-8"
    res.out.print( resHtml )
  }
}
internal const class WRequestHandler : Actor
{
  private static const Str appFactoryKey := "WServer.appFactory"
  
  static const WRequestHandler instance := make
  private new make()
    : super( ActorPool() )
  {
  }

  protected override Obj? receive(Obj? msg)
  {
    if( msg == null )
    {
      WLog.err( "WServer received null message" )
      return null
    }
    
    if( msg is WReq )
    {
      WReq req := msg
      Str sessionID := req.sessionID
      WLog.info( "Received request from session: $sessionID" )
      WApp? app := locals.get( sessionID )
      if( app == null )
      {
        WAppFactory? appFactory := locals.get( appFactoryKey )
        if( appFactory == null )
        {
          WLog.err( "AppFactory not initialized" )
          return null
        }
        
        app = appFactory.create
        app.setSessionID( sessionID )
        locals.set( sessionID, app )
        WLog.info( "Application ${app.typeof.name} created for session: $sessionID" )
      }
      
      return app.handleRequest( req )
    }

    if( msg is WAppFactory )
    {
      WAppFactory appFactory := msg
      locals.set( appFactoryKey, appFactory )
      return null
    }

    WLog.err( "WServer received incorrect message: ${msg.typeof.name}" )
    return null
  }
}
class WApp
{
  Int id := WSequence.next
  Str? sessionID
  Str title := "webtoolkit"
  Str onLoad := ""
  WWidgetContainer root := WWidgetContainer() 

  Void setSessionID( Str sessionID )
  {
    this.sessionID = sessionID
  }
  
  Str handleRequest( WReq req )
  {
    WLog.info( "handle request by app with ID = $id" )
    
    // process actions
    if( req.params != null )
    {
      req.params.each |Str value, Str key|
      {
        WLog.info( "param: '$key'->'$value'" )
        Str[] actionAndID := key.split('_')
        WLog.info( "actionAndID: '$actionAndID'" )
        Str action := actionAndID.get(0)
        Int id := actionAndID.get(1).toInt
        WWidget? widget := root.find( id )
        if( widget != null )
          widget.action( action, value )
      }
    }
	
    // render html
    Str html := "<HTML><HEAD><TITLE>$title</TITLE></HEAD><BODY>"
    html += root.render
    html += "</BODY></HTML>"
    return html
  }
}

Sorry for size of this post. I think sources explain itself better. It's my first fan's project. If you'll see my mistakes in undestanding of fan tell me, please.

brian Sun 23 May 2010

My first thought looking at all that code, is that is sort of reminiscent of the pain that Java developers go thru to abstract their design. So I would pose this question - what is the absolute simplest way to write your code in the least number of lines?

There seems like there are two basic concepts:

  • a simple "framework" class which is used to build widget trees and route action requests
  • an actual page built up of widgets

Unless there is truly some value in the other classes and abstractions, I would try to simplify the design into a single WebMod or Weblet class that handles the framework side of things.

You might want to take a look at the old Fantom Widget code which was the original webapp pod.

pavel_repin Mon 24 May 2010

Thank you for links. I see a few useful techniques. But concept is not usual for me. Need time to change my way of thinking.

Login or Signup to reply.