ID: 20449 Updated by: [EMAIL PROTECTED] Reported By: [EMAIL PROTECTED] Status: Feedback Bug Type: Session related Operating System: redhat 7.3 PHP Version: 4.4.0-dev New Comment:
Missed a couple of words in the above. "will randomly be deleted" I meant to write. Previous Comments: ------------------------------------------------------------------------ [2002-11-17 12:35:37] [EMAIL PROTECTED] Your handler isn't very good. As far as I can tell you never update the expiry value which means that your sessions will randomly (because the gc hook is called on a probability) after 5400 seconds (90 minutes) after initial creation. A quick fix would be to never expire sessions by setting: session.gc_probability = 0 In your php.ini file. Or, better yet, fix your handler to update the expiry time when writing to a session. This expiry time is supposed to be idle session time, not an absolute the way you have it. I would suggest letting MySQL handle the timestamp and using a schema that looks like this: id char(32) NOT NULL, data text, ts timestamp, PRIMARY KEY (id) And instead of that ugly INSERT/UPDATE thing you have for your write hook, use: $data = addslashes($data); mysql_query("replace into $table (id,data) values('$id','$data')") or error_log("write: ".mysql_error()."\n",3,"/tmp/errors.log"); The gc hook would just be: function gc($max_time) { global $table; mysql_query( "delete from $table where UNIX_TIMESTAMP(ts)<UNIX_TIMESTAMP()-$max_time") or error_log("gc: ".mysql_error()."\n",3,"/tmp/errors.log"); return true; } But try setting gc_probability to 0 just to verify that this is indeed the cause of the lost sessions. If it is, then you need to slap yourself hard as the code is doing exactly what you told it to do. ------------------------------------------------------------------------ [2002-11-17 12:19:36] [EMAIL PROTECTED] I'm reopening this bug as I've provided more feedback. Please yell at me if I'm not doing this right :) ------------------------------------------------------------------------ [2002-11-17 12:17:39] [EMAIL PROTECTED] here is my session save handler. Pretty much taken from phpbuilder.com article. Important to note that I have never seen a mysql error in my php_errors log. <? $GLOBALS["SESS_DBHOST"] = "not shown"; /* database server hostname */ $GLOBALS["SESS_DBNAME"] = "not shown"; /* database name */ $GLOBALS["SESS_DBUSER"] = "not shown"; /* database user */ $GLOBALS["SESS_DBPASS"] = "not shown"; /* database password */ $GLOBALS["SESS_DBH"] = ""; $GLOBALS["SESS_LIFE"] = 5400; function sess_open($save_path, $session_name) { if (!$GLOBALS["SESS_DBH"] = mysql_pconnect($GLOBALS["SESS_DBHOST"], $GLOBALS["SESS_DBUSER"], $GLOBALS["SESS_DBPASS"])) { echo "<li>Can't connect to " . $GLOBALS["SESS_DBHOST"] . " as " . $GLOBALS["SESS_DBUSER"]; echo "<li>MySQL Error: ", mysql_error(); die; } if (! mysql_select_db($GLOBALS["SESS_DBNAME"], $GLOBALS["SESS_DBH"])) { echo "<li>Unable to select database " . $GLOBALS["SESS_DBNAME"]; die; } return true; } function sess_close() { return true; } function sess_read($key) { $qry = "SELECT value FROM sessions WHERE sesskey = '$key' AND expiry > " . time(); $qid = mysql_query($qry, $GLOBALS["SESS_DBH"]); if (list($value) = mysql_fetch_row($qid)) { return $value; } return ""; } function sess_write($key, $val) { $expiry = time() + $GLOBALS["SESS_LIFE"]; $value = addslashes($val); $qry = "INSERT INTO sessions VALUES ('$key', $expiry, '$value')"; $qid = mysql_query($qry, $GLOBALS["SESS_DBH"]); if (! $qid) { $qry = "UPDATE sessions SET expiry = $expiry, value = '$value' WHERE sesskey = '$key' AND expiry > " . time(); $qid = mysql_query($qry, $GLOBALS["SESS_DBH"]); } return $qid; } function sess_destroy($key) { $qry = "DELETE FROM sessions WHERE sesskey = '$key'"; $qid = mysql_query($qry, $GLOBALS["SESS_DBH"]); return $qid; } function sess_gc($maxlifetime) { $qry = "DELETE FROM sessions WHERE expiry < " . time(); $qid = mysql_query($qry, $GLOBALS["SESS_DBH"]); return mysql_affected_rows($GLOBALS["SESS_DBH"]); } //now that we have defined everything, set the save handler session_set_save_handler( "sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc"); ?> ------------------------------------------------------------------------ [2002-11-17 12:13:59] [EMAIL PROTECTED] Here is my cart script. The only special thing I do before this is to initiate the session. I always call session_id(an id) before session_start because I can't rely on cookies. The only time I do not call session_id is when the visitor first comes to the site. manage_products() is called on the cart.php page when a user either adds an item or updates an item. Also, cart.php is the only page that manipulates the cart. Every other page simply display cart info. (including the order page) <? function print_cart() { include("cart_display.html"); } function printMiniCart() { include("cart_mini_display.html"); } function print_non_editable_cart() { include("cart_final_display.html"); } function manage_products() { //get the product properties from the get or post variables $prod_id = $_POST["product_id"]; $qty = $_POST["qty"]; if(isset($_POST["options"])) $options = $_POST["options"]; //this is the case when updating else $options = $_POST["color"] . "-" . $_POST["size"]; //blow up the item if it is already in the cart if(isset($_SESSION["cart"][$prod_id])) { //product already in cart $products = explode("+++", $_SESSION["cart"][$prod_id]); $prc = count($products); $product_found = false; for($i=0; $i < $prc; $i++) { //now explode the inner workings of each subproduct $subproduct = explode("|", $products[$i]); //the array of subproduct looks like this //options = $subproduct[0]; //qty = $subproduct[1]; if($subproduct[0] == $options) { //product being added is same as current subproduct. //update the qty $subproduct[1] = $qty; $product_found = true; } //rebuild the subproduct $products[$i] = implode("|", $subproduct); } if(!$product_found) { //product configuration not found in cart. add it to cart $products[] = $options . "|" . $qty; } //rebuild the product string $_SESSION["cart"][$prod_id] = implode("+++", $products); } else { //easy case. Product not in cart //simply add it //build the product identification string $products[] = $options . "|" . $qty; $_SESSION["cart"][$prod_id] = implode("+++", $products); } return true; } ?> ------------------------------------------------------------------------ [2002-11-17 12:06:13] [EMAIL PROTECTED] ok. This is really frustrating. I wrote a script that simulates a cart. I use fopen once to add something to the cart, and a second time to check the cart. (I use uniqid to create a session id and pass it through the url) The script simulates it 100 times. Then, via a interface and iframes, I have six frames loading the test script. See - http://www.t-shirtking.com/temp/testcart.html This raises the load a little. However, as you will see, it checks out everytime. However, this morning I check my e-mail and find a dozen more messages from the order page that tells me someones cart was empty. Next message, I'm gonna show you my cart. It isn't too complicated. ------------------------------------------------------------------------ The remainder of the comments for this report are too long. To view the rest of the comments, please view the bug report online at http://bugs.php.net/20449 -- Edit this bug report at http://bugs.php.net/?id=20449&edit=1