the state pattern

The state pattern becomes useful if your object needs to react differently to different states. Let us assume you are writing an SMTP Proxy. SMTP works in sequences (connection establishment, ehlo/helo, mail, rcpt, data(, …), quit). Each sequence can have different error/success codes which are not available in another. For example you would only return 220 on connection establishment, not 250. And you would return 221 on quit, not 250. So if you are writing a server which works like this, you will most likely use an if then else or switch. The state-pattern would be another method to solve this. However, a more simple example which shows the pros and cons of the state pattern is an activity-based-sleep.

Assume you are writing a server application in PHP. Most likely you do have while(true){ … }. If there is no sleep() at the end of the while you’re wasting cpu cycles and you’ll reach up to 100% cpu usage. Now the documentation suggests to use usleep(200000) to not waste too much. The simple variant would be to use:

if($connections > 0){
  usleep(200000);
} else {
  sleep(1);
}

A few weeks later you do notice, that with 100 connected clients the first client needs to wait 200 000 microseconds * 100 to be active again (20 seconds!). Your solution is to reduce the usleep depending on the connected clients:

$udelay = 200000;
if($connections > 0){
   usleep($udelay / $connections);
} else {
   sleep(1);
}

Now you need to implement additional code to verify $connections is less than $udelay. You might want to have a tiny default usleep for such cases to avoid 0 (say, 100?) – And then you do notice that your server is not used between 22:00 and 08:00 and you’d like to make it more idle to save additional resources by sleeping 3 instead of 1 seconds IF it has been idle for 10 cycles.

$idleCounter = 0;
// in microseconds
$activeDelay = 200000;
// in seconds
$inactiveDelay = 1;
 
while(true) {
  // do something...
 
  if($connections > 0) {
    // resetting the idleCounter as soon as we have a new connection
    $idleCounter = 0;
    if($connections < $activeDelay) {
      usleep($activeDelay / $connections);
    } else {
      usleep(100);
    }
  } else {
    if($idleCounter > 10){
      sleep(3);
    } else {
      sleep(1);
      $idleCounter++;
    }
  }
}

Your nice if-then-else construct is growing and growing and growing. Obviously you could just write a method just for that stuff to keep the contents of the while() clean – Still the more you want/need that if-then-else might keep growing making the whole object (worst case) unreadable.

For the state pattern you’ll use an interface which defines the methods each state-object needs to have. In our case that would be the method „sleep“ (actually, I just did not had a better name for activity-based-sleep)

/**
 * Class WebSocketsServerState
 * @package WebSocketsServer\helpers\interfaces
 */
interface WebSocketsServerState
{
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    function sleep(ServerStateContext $context, $connections);
}

As you can see, the method sleep gets the context object and the amount of currently open connections. The context object looks like this:

/**
 * Class ServerStateContext
 * @package WebSocketsServer\controllers\states
 */
class ServerStateContext
{
    /**
     * @var WebSocketsServerState $state
     */
    private $state;
 
    public function __construct()
    {
        $this->setState(new IdleServerState());
    }
 
    /**
     * @param WebSocketsServerState $state
     */
    public function setState(WebSocketsServerState $state)
    {
        $this->state = $state;
    }
 
    /**
     * @param int $connections
     */
    public function sleep($connections)
    {
        $this->state->sleep($this, $connections);
    }
}

No rocket science. Later I’ll call sleep($connections) from within my while(). The context class calls sleep of the currently active state object – which is by default as you can see in the constructor the idle state.

/**
 * Class IdleServerState
 * @package WebSocketsServer\controllers\states
 */
class IdleServerState implements WebSocketsServerState
{
    /**
     * @var int time to sleep in seconds
     */
    private $delay = 2;
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to active if there are connections
        if ($connections > 0) {
            $context->setState(new ActiveServerState());
        } else {
            sleep($this->delay);
        }
    }
}

Here you can see how the object will update its state and use ActiveServerState if there are more than 0 connections.

/**
 * Class ActiveServerState
 * @package WebSocketsServer\controllers\states
 */
class ActiveServerState implements WebSocketsServerState
{
    /**
     * @var int time to sleep in microseconds
     */
    private $udelay = 200000;
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to idle if there are no connections
        if ($connections < = 0) {
            $context->setState(new IdleServerState());
        } else {
            usleep($this->udelay);
        }
    }
}

And here how the object will go back to idle state if there are no connections. Our while would look like this:

        $ctx = new ServerStateContext();
        while (true) {
 
            $ctx->sleep($connections);
        }

Now you can update the IdleState Class to have an internal counter, which is if over 10 increasing the sleep and setting it to zero as soon as we’d switch to the active state:

/**
 * Class IdleServerState
 * @package WebSocketsServer\controllers\states
 */
class IdleServerState implements WebSocketsServerState
{
    /**
     * @var int time to sleep in seconds
     */
    private $delay = 2;
 
    private $cycle = 0;
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to active if there are connections
        if ($connections > 0) {
            $this->cycle = 0;
            $context->setState(new ActiveServerState());
        } else {
            if($this->cycle > 10){
              sleep(5); 
            } else {
              sleep($this->delay);
            }
        }
    }
}

That keeps the while and the main-class clean. And once you want to implement a third state (one for throttled clients? deep sleep instead of the if then else in IdleServerState?) it stays clean.

However – in the beginning of my thread I’ve written that this example shows the pros and the cons. I hope you did get the pros from the above examples, another good thing about the state pattern is that one has to think about the states and their exact jobs. For the cons, we just wrote 3 classes and 1 interface just for a simple if-then-else. Usually the state-pattern makes a lot of sense if you have lots of methods.

Update
Till had a few questions/enhancements, he asked „in your state objects you are making a differentiation, if condition then modify state else sleep. Shouldn’t it modify the state if condition x happens and then just normally call the sleep? Otherwise you would need to wait one iteration till the state object does what it should.“ Yes and no. For the switch from active to idle okay. For the switch from idle to active I’d ignore that to have the next iteration reached earlier. Then he said „[..] and at your while() I would use something like (!($this->state instanceof StateDisconnected)) or ($this->isNotDisconnected()) instead of while(true) where as the condition would be in isNotDisconnected.“ Yup!

No Comments

Post a Comment