portNumber = $portNumber; $this->timeoutSeconds = $timeoutSeconds; $this->pollSleepMicros = (int)(1000000.0 * $pollSleepSeconds + 0.5); $this->pid = getmypid(); } // =============================================================== // Method : execute // Returns: true on success, false on error // // obj -> a reference to an object that has either: // // 1) only one method (outside of constructors/magic methods) // 2) a method with the same // name as this function (__FUNCTION__) // // errMsg -> populated on error // // obj's method should also return true on success, false // on error and should take one argument (errMsg) // passed by reference. An Example: // // class Example // { // function doit(&$errMsg) // { // print("doit()\n"); // return(true); // } // } // // $slock = new SLock(5454); // $ex = new Example(); // // if(!$slock->execute($ex, $errMsg)) // die($errMsg); // =============================================================== function execute(&$obj, &$errMsg) { if(!is_object($obj)) { $errMsg = "SLock::execute - 'obj' not an object"; return(false); } // If $obj has only one method we'll call that // method, otherwise we expect $obj to have a // method named: __FUNCTION__ $methods = SLock::getMethods($obj); $method = (count($methods) > 1 ? __FUNCTION__ : $methods[0]); if(!SLock::chkMethod(1, $method, $obj, $errMsg)) return(false); // Acquire the lock and execute $obj->$method, note that // the lock must always be released if(!$this->acquire($errMsg)) return(false); if($obj->$method($errMsg)) return($this->release($errMsg)); if(!$this->release($endErrMsg)) $errMsg .= ", " . $endErrMsg; return(false); } // =============================================================== // Method : invokeClientData // Returns: true on success, false on error // // someFunction -> a function taking two parameters // both passed by reference (clientData, errMsg) // // clientData -> passed by reference, allows someFunction // to communicate information back to the caller // // errMsg -> populated on error // // An Example: // // function someFunction(&$clientData, &$errMsg) // { // $theTime = time(); // // if($theTime % 2 != 0) // { // $errMsg = "Time ($theTime) not even"; // return(false); // } // // $clientData = $theTime; // // return(true); // } // // $slock = new SLock(5454); // $cd = false; // // if(!$slock->invokeClientData("someFunction", $cd, $errMsg)) // die($errMsg . "\n"); // // printf("even time: %s\n", $cd); // =============================================================== function invokeClientData($someFunction, &$clientData, &$errMsg) { if(!SLock::chkMethod(2, $someFunction, false, $errMsg)) return(false); // Acquire lock, call $someFunction with // $clientData, release lock if(!$this->acquire($errMsg)) return(false); if($someFunction($clientData, $errMsg)) return($this->release($errMsg)); if(!$this->release($endErrMsg)) $errMsg .= ", " . $endErrMsg; return(false); } // =============================================================== // Method : invoke // Returns: true on success, false on error // // someFunction -> a function taking one parameter passed by // reference (errMsg) // // errMsg -> populated on error // // An Example: // // function someFunction(&$errMsg) // { // for($i = 0; $i < 5; $i ++) // { // print("Sleeping in someFunction() ...\n"); // sleep(1); // } // // return(true); // } // // $slock = new SLock(5454); // $cd = false; // // if(!$slock->invoke("someFunction", $errMsg)) // die($errMsg . "\n"); // =============================================================== function invoke($someFunction, &$errMsg) { if(!SLock::chkMethod(1, $someFunction, false, $errMsg)) return(false); // Acquire lock, call $someFunction, release lock if(!$this->acquire($errMsg)) return(false); if($someFunction($errMsg)) return($this->release($errMsg)); if(!$this->release($endErrMsg)) $errMsg .= ", " . $endErrMsg; return(false); } // =============================================================== // Method : call // Returns: true on success, false on error // // someFunction -> a function taking no parameters // // errMsg -> populated on error // // An Example: // // function someFunction() // { // for($i = 0; $i < 5; $i ++) // { // print("Sleeping in someFunction() ...\n"); // sleep(1); // } // } // // $slock = new SLock(5454); // $cd = false; // // if(!$slock->call("someFunction", $errMsg)) // die($errMsg . "\n"); // =============================================================== function call($someFunction, &$errMsg) { if(!SLock::chkMethod(0, $someFunction, false, $errMsg)) return(false); // Acquire lock, call $someFunction, release lock if(!$this->acquire($errMsg)) return(false); $someFunction(); return($this->release($errMsg)); } // =============================================================== // Method : acquire // Returns: true on success, false on error // // Will return false if we are already bound to // the port specified in the constructor or if we are // unable to bind within '$timeoutSeconds' seconds. // =============================================================== function acquire(&$errMsg) { if($this->theSocket !== false) { $errMsg = "SLock has already been acquired"; return(false); } $s = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if(!$s) { $errMsg = "Error calling: socket_create"; return(false); } $start = SLock::getMicroTime(); $first = true; while(true) { $now = SLock::getMicroTime(); if(@socket_bind($s, "0.0.0.0", $this->portNumber)) { if($this->dbg) { printf( "%-5d - %-16s - %-4s - Lock Acquired\n", $this->pid, $now, $this->portNumber ); } break; } if($first) { if($this->dbg) { printf( "%-5d - %-16s - %-4s - Waiting For Lock\n", $this->pid, $now, $this->portNumber ); } $first = false; } // Check for timeout if so requested ... if($this->timeoutSeconds > 0) { $elapsed = $now - $start; if($elapsed >= $timeoutSeconds) { socket_close($s); $errMsg = sprintf( "Timed out (%s seconds) " . "trying to bind to port: %d", $this->timeoutSeconds, $this->portNumber ); return(false); } } usleep($this->pollSleepMicros); } // If we're here, we managed to bind to our port // within any specified timeout ... $this->theSocket = $s; return(true); } // =============================================================== // Method : release // Returns: true on success, false on error // // Will return false if we are not currently bound to a port // =============================================================== function release(&$errMsg) { if($this->theSocket === false) { $errMsg = "SLock has not yet been acquired"; return(false); } socket_close($this->theSocket); if($this->dbg) { printf( "%-5d - %-16s - %-4s - Lock Released\n", $this->pid, SLock::getMicroTime(), $this->portNumber ); } $this->theSocket = false; return(true); } // ============================================================ // Static Methods // ============================================================ // The 'do' methods that follow are all static variants // of execute, invoke, invokeClientData and call. They exist // for the sake of convenience as they do not require the // user to first create an SLock object. function doExecute(&$obj, $portNumber, &$errMsg) { $slock = new SLock($portNumber); return($slock->execute($obj, $errMsg)); } function doExecuteTimeOut(&$obj, $portNumber, $timeOut, &$errMsg) { $slock = new SLock($portNumber, $timeOut); return($slock->execute($obj, $errMsg)); } function doInvoke($someFunction, $portNumber, &$errMsg) { $slock = new SLock($portNumber); return($slock->invoke($someFunction, $errMsg)); } function doInvokeTimeOut( $someFunction, $portNumber, $timeOut, &$errMsg ) { $slock = new SLock($portNumber, $timeOut); return($slock->invoke($someFunction, $errMsg)); } function doInvokeClientData( $someFunction, &$clientData, $portNumber, &$errMsg ) { $slock = new SLock($portNumber); return( $slock->invokeClientData($someFunction, $clientData, $errMsg) ); } function doInvokeClientDataTimeOut( $someFunction, &$clientData, $portNumber, $timeOut, &$errMsg ) { $slock = new SLock($portNumber, $timeOut); return( $slock->invokeClientData($someFunction, $clientData, $errMsg) ); } function doCall($someFunction, $portNumber, &$errMsg) { $slock = new SLock($portNumber); return($slock->call($someFunction, $errMsg)); } function doCallTimeOut( $someFunction, $portNumber, $timeOut, &$errMsg ) { $slock = new SLock($portNumber, $timeOut); return($slock->call($someFunction, $errMsg)); } function chkMethod($nParameters, $theMethod, $theObject, &$errMsg) { $version = phpversion(); $major = preg_replace("/\..*/", "", $version); if($major < 5) return(true); // For php version >= 5, we can do additional checking // to ensure the number of parameters is correct and // that all parameters are passed by reference $prefix = $theMethod; if(!is_object($theObject)) { if(!function_exists($theMethod)) { $errMsg = "Function not found: '$theMethod'"; return(false); } $r = new ReflectionFunction($theMethod); } else { if(!method_exists($theObject, $theMethod)) { $errMsg = "Method not found in object: '$theMethod'"; return(false); } $r = new ReflectionMethod( get_class($theObject), $theMethod ); $prefix = "obj->" . $prefix; } $rp = $r->getParameters(); if(($n = count($rp)) != $nParameters) { $errMsg = sprintf( "Expected %s to take %s parameters%s, not %s", $prefix, $nParameters, $nParameters == 1 ? "" : "s", $n ); return(false); } for($i = 0; $i < $n; $i ++) { if(!$rp[$i]->isPassedByReference()) { $errMsg = sprintf( "Expected parameter %s to %s to be a reference", $i + 1, $prefix ); return(false); } } return(true); } function getMicroTime() { $theValues = split(" ", microtime()); $microTime = (double)$theValues[0] + (double)$theValues[1]; return($microTime); } function getMethods($obj) { // Acquire a list of all of $obj's methods except // for any whose name matches the class name (the // constructor) or starts with '__' (magic methods, php5) $theClass = get_class($obj); $all = get_class_methods($theClass); $regex = "/^($theClass$|__)/i"; $filtered = array(); foreach($all as $entry) if(!preg_match($regex, $entry)) $filtered[] = $entry; return($filtered); } } } ?>