title: PHP Socket Server Memory Leak
body: <p>I have checked the memory whilst sending and receiving data over one connection, and I appear to be correctly clearing variables, as the memory returns to its previous value.</p>
  
  <p>But for some reason if I make a new connection, then close the connection, memory is leaked. I believe the problem may be occurring when a socket is accepted.</p>
  
  <p>I am using PHP 5.2.10</p>
  
  <p>Hopefully one of you can find the time to have a play with the source and figure out where its gone wrong. Thanks in advance</p>
  
  <pre><code>&lt;?php
      Class SuperSocket
          {
              var $listen = array();
              var $status_listening = FALSE;
              var $sockets = array();
              var $event_callbacks = array();
              var $recvq = 1;
              var $parent;
              var $delay = 100; // 10,000th  of a second
              var $data_buffer = array();
  
              function SuperSocket($listen = array('127.0.0.1:123'))
                  {
                      $listen = array_unique($listen);
                      foreach ($listen as $address)
                          {
                              list($address, $port) = explode(":", $address, 2);
                              $this-&gt;listen[] = array("ADDR" =&gt; trim($address), "PORT" =&gt; trim($port));
                          };
                  }
  
              function start()
                  {
                      if ($this-&gt;status_listening)
                          {
                              return FALSE;
                          };
                      $this-&gt;sockets = array();
                      $cursocket = 0;
                      foreach ($this-&gt;listen as $listen)
                          {
                              if ($listen['ADDR'] == "*")
                                  {
                                      $this-&gt;sockets[$cursocket]['socket'] = socket_create_listen($listen['PORT']);
                                      $listen['ADDR'] = FALSE;
                                  }
                              else
                                  {
                                      $this-&gt;sockets[$cursocket]['socket'] = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
                                  };
                              if ($this-&gt;sockets[$cursocket]['socket'] &lt; 0)
                                  {
                                      return FALSE;
                                  };
                              if (@socket_bind($this-&gt;sockets[$cursocket]['socket'], $listen['ADDR'], $listen['PORT']) &lt; 0)
                                  {
                                      return FALSE;
                                  };
                              if (socket_listen($this-&gt;sockets[$cursocket]['socket']) &lt; 0)
                                  {
                                      return FALSE;
                                  };
                              if (!socket_set_option($this-&gt;sockets[$cursocket]['socket'], SOL_SOCKET, SO_REUSEADDR, 1))
                                  {
                                      return FALSE;
                                  };
                              if (!socket_set_nonblock($this-&gt;sockets[$cursocket]['socket']))
                                  {
                                      return FALSE;
                                  };
                              $this-&gt;sockets[$cursocket]['info'] = array("ADDR" =&gt; $listen['ADDR'], "PORT" =&gt; $listen['PORT']);
                              $this-&gt;sockets[$cursocket]['channels'] = array();
                              $this-&gt;sockets[$cursocket]['id'] = $cursocket;
                              $cursocket++;
                          };
                      $this-&gt;status_listening = TRUE;
                  }
  
              function new_socket_loop(&amp;$socket)
                  {
                      $socket =&amp; $this-&gt;sockets[$socket['id']];
                      if ($newchannel =  @stream_socket_accept($socket['socket'], 0));//@socket_accept($socket['socket']))
                          {
                              socket_set_nonblock($newchannel);
                              $socket['channels'][]['socket'] = $newchannel;
                              $channel = array_pop(array_keys($socket['channels']));
                              $this-&gt;remote_address($newchannel, $remote_addr, $remote_port);
                              $socket['channels'][$channel]['info'] = array('ADDR' =&gt; $remote_addr, 'PORT' =&gt; $remote_port);
                              $event = $this-&gt;event("NEW_SOCKET_CHANNEL");
                              if ($event)
                              $event($socket['id'], $channel, $this);
                          };
                  }
  
      function endswith($string, $test) {
          $strlen = strlen($string);
          $testlen = strlen($test);
          if ($testlen &gt; $strlen) return false;
          return substr_compare($string, $test, -$testlen) === 0;
      }
  
              function recv_socket_loop(&amp;$socket)
                  {
                      $socket =&amp; $this-&gt;sockets[$socket['id']];
                      foreach ($socket['channels'] as $channel_id =&gt; $channel)
                          {
                              unset($buffer);#Flush buffer
                              $status = @socket_recv($channel['socket'], $buffer, $this-&gt;recvq, 0);
                              if ($status === 0 &amp;&amp; $buffer === NULL)
                                  {
                                      $this-&gt;close($socket['id'], $channel_id);
                                  }
                              elseif (!($status === FALSE &amp;&amp; $buffer === NULL))
                                  {
                                      $sockid = $socket['id'];
                                      if(!isset($this-&gt;data_buffer[$sockid]))
                                          $this-&gt;data_buffer[$sockid]='';
  
                                      if($buffer!="\r"&amp;&amp;$buffer!="\n")
                                      {
                                          //Putty ends with \r\n
                                          $this-&gt;data_buffer[$sockid].=$buffer;
                                      }
                                      else if($buffer!="\n") //ignore the additional newline char \n
                                      {
                                          $event = $this-&gt;event("DATA_SOCKET_CHANNEL");
                                          if ($event)
                                              $event($socket['id'], $channel_id, $this-&gt;data_buffer[$sockid], $this);
                                          unset($this-&gt;data_buffer[$sockid]);
                                      }
  
                                  };
                          }
                  }
  
              function stop()
                  {
                      $this-&gt;closeall();
                      $this-&gt;status_listening = FALSE;
                      foreach ($this-&gt;sockets as $socket_id =&gt; $socket)
                          {
                              socket_shutdown($socket['socket']);
                              socket_close($socket['socket']);
                          };
                      $event = $this-&gt;event("SERVER_STOP");
                      if ($event)
                      $event($this);
                  }
  
              function closeall($socket_id = NULL)
                  {
                      if ($socket_id === NULL)
                          {
                              foreach ($this-&gt;sockets as $socket_id =&gt; $socket)
                                  {
                                      foreach ($socket['channels'] as $channel_id =&gt; $channel)
                                          {
                                              $this-&gt;close($socket_id, $channel_id);
                                          }
                                  }
                          }
                      else
                          {
                              foreach ($this-&gt;sockets[$socket_id]['channels'] as $channel_id =&gt; $channel)
                                  {
                                      $this-&gt;close($socket_id, $channel_id);
                                  };
                          };
                  }
  
              function close($socket_id, $channel_id)
                  {
                      unset($this-&gt;data_buffer[$socket_id]); //clear the sockets data buffer
                      $arrOpt = array('l_onoff' =&gt; 1, 'l_linger' =&gt; 1);
                      @socket_shutdown($this-&gt;sockets[$socket_id]['channels'][$channel_id]['socket']);
                      @socket_close($this-&gt;sockets[$socket_id]['channels'][$channel_id]['socket']);
                      $event = $this-&gt;event("LOST_SOCKET_CHANNEL");
                      if ($event)
                      $event($socket_id, $channel_id, $this);
                  }
  
              function loop()
                  {
                      while ($this-&gt;status_listening)
                          {
                              usleep($this-&gt;delay);
                              foreach ($this-&gt;sockets as $socket)
                                  {
                                      $this-&gt;new_socket_loop($socket);
                                      $this-&gt;recv_socket_loop($socket);
                                  };
                              $event = $this-&gt;event("END_SOCKET_CHANNEL");
                              if ($event)
                              $event($this);
                          };
                  }
  
              function write($socket_id, $channel_id, $buffer)
                  {   
                      @socket_write($this-&gt;sockets[$socket_id]['channels'][$channel_id]['socket'], $buffer);
                      @socket_write($this-&gt;sockets[$socket_id]['channels'][$channel_id]['socket'], 'Server memory usage: '.memory_get_usage().'/'.memory_get_peak_usage(true)."\r\n");
                  }
  
              function get_channel_info($socket_id, $channel_id)
                  {
                      return $this-&gt;sockets[$socket_id]['channels'][$channel_id]['info'];
                  }
  
              function get_socket_info($socket_id)
                  {
                      $socket_info = $this-&gt;sockets[$socket_id]['info'];
                      if (empty($socket_info['ADDR']))
                          {
                              $socket_info['ADDR'] = "*";
                          };
                      return $socket_info;
                  }
  
              function get_raw_channel_socket($socket_id, $channel_id)
                  {
                      return $this-&gt;sockets[$socket_id]['channels'][$channel_id]['socket'];
                  }
  
              function remote_address($channel_socket, &amp;$ipaddress, &amp;$port)
                  {
                      socket_getpeername($channel_socket, $ipaddress, $port);
                  }
  
              function event($name)
                  {
                      if (isset($this-&gt;event_callbacks[$name]))
                      return $this-&gt;event_callbacks[$name];
                  }
  
              function assign_callback($name, $function_name)
                  {
                      $this-&gt;event_callbacks[$name] = $function_name;
                  }
          };
  
      ?&gt;
  </code></pre>
  
  <p>Server.php</p>
  
  <pre><code>include("supersocket.class.php");
  
  function startswith($string, $test) {
      return strpos($string, $test, 0) === 0;
  }
  
  function newdata($socket_id, $channel_id, $buffer, &amp;$server)
      {
          //$server-&gt;write($socket_id, $channel_id, "&gt;".$buffer."\r\n");
          if($buffer=="STOP")
          {
              $server-&gt;stop();
          }
          else if($buffer=="DATETIME")
          {
              $server-&gt;write($socket_id, $channel_id, "&gt;".date("dmYHis")."\r\n");
          }
          else
          {
              $server-&gt;write($socket_id, $channel_id, "&gt;BAD\r\n");
          }
  
      };
  
  function newclient($socket_id, $channel_id, &amp;$server)
  {
      $server-&gt;write($socket_id, $channel_id, "HEADER\n\r");
  }
  
  $socket = new SuperSocket(array('127.0.0.1:12345')); 
  $socket-&gt;assign_callback("DATA_SOCKET_CHANNEL", "newdata");
  $socket-&gt;assign_callback("NEW_SOCKET_CHANNEL", "newclient");
  $socket-&gt;start();
  //set_time_limit(60*2);
  set_time_limit(60*60*24*5); //5 days
  $socket-&gt;loop();
  </code></pre>
  
  <p>Edit: sorry you might need to change the socket accept back to:
  if ($newchannel =  @socket_accept($socket['socket']))</p>