
Patrick O'Lone suggests the following idea which sounds interesting to
add as an optional mode of Cache_Lite class. 
(still not tested in the Cache_Lite context)

-------------------------------------------------------------------------
If you use the flags:

ignore_user_abort(true);

$fd = dio_open($szFilename, O_CREATE | O_EXCL | O_TRUNC | O_WRONLY,
0644);
if (is_resource($fd)) {

   dio_fcntl($fd, F_SETLKW, array('type' => F_WRLCK));
   dio_write($fd, $szBuffer);
   dio_fcntl($fd, F_SETLK, array('type' => F_UNLCK));
   dio_close($fd);

}

ignore_user_abort(false);

Only the first process will attempt to create a file. Additional
processes will see that a file already exists (at the system level), and
will fail. Another thing to note is that the file descriptor must be
opened using dio_open(), and certain features, like fgets() won't work
with it. If your just doing a raw write, dio_write() should be just
fine. The dio_read() function should be used like:

$fd = dio_open($szFilename, O_RDONLY|O_NONBLOCK, 0644);
if (is_resource($fd)) {

   dio_fcntl($fd, F_SETLKW, array('type' => F_RDLCK));
   $szBuffer = dio_read($fd, filesize($szFilename));
   dio_fcntl($fd, F_SETLK, array('type' => F_UNLCK));
   dio_close($fd);

}

You still use locking to ensure that a write process can finish before
another attempts to read the file. We also set non-blocking mode in read
mode so that multiple readers can access the same resource at the same
time. NOTE: Direct I/O support must be compiled into PHP for these
features to work (--enable-dio).
-------------------------------------------------------------------------



Another optionnal mode could be (Mike Benoit's interesting idea) :
(not tested)

-------------------------------------------------------------------------
        However recently I ran in to a problem with it and one of the projects
I'm working on (http://phpgacl.sourceforge.net/). phpGACL caches _many_
( on the order of 10,000-100,000) very small pieces of data, when I
tried using Cache Lite for this, my file system didn't like it much
since there were so many files in a single directory. 

So I patched Cache Lite to support a hashed directory structure. ie:

<root cache dir>/<group>/<number between 0-999>/<chopped CRC32>

../default
../default/081
../default/081/081162803
../default/215
../default/215/215106191
../default/333
../default/333/33376174
../default/366
../default/366/366703566
../default/500

I ran a few rough benchmarks, and this method was slower until the cache
file numbers started growing to over 30,000 or so. Another advantage to
doing it this way is it's really easy to clear the cache for a specific
group. :)

The other change I made was to make it so cache data that was read from
the file system was also stored in memory for subsequent reads. Scripts
that actually do this should see quite a large increase in performance,
if the script doesn't actually hit a cache entry more then once, it
obviously slows things down slightly and uses more memory. Currently it
ignores cache expire times when it stores data in memory, but in theory
this shouldn't be a big deal.

<?
/*
 * phpGACL - Generic Access Control List - Hashed Directory Caching. 
 * Copyright (c) 2002-2003 Mike Benoit
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For questions, help, comments, discussion, etc., please join the
 * phpGACL mailing list. http://sourceforge.net/mail/?group_id=57103
 *
 * You may contact the author of phpGACL by e-mail at:
 * ipso@snappymail.ca
 *
 * The latest version of phpGACL can be obtained from:
 * http://phpgacl.sourceforge.net/
 *
 */
require_once("Cache_Lite.php");

define('DIR_SEP', DIRECTORY_SEPARATOR);

class Hashed_Cache_Lite extends Cache_Lite
{
    /**
    * Memory caching variable
    * 
    * @var array $_memoryCache
    */
    var $_memoryCache = NULL;

    /**
    * Test if a cache is available and (if yes) return it - Original version by Fabien MARTY <fab@php.net>	
    *
    * @param string $id cache id
    * @param string $group name of the cache group
    * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
    * @return string data of the cache (or false if no cache available)
    * @access public
    */
    function get($id, $group = 'default', $doNotTestCacheValidity = false)
    {
        $this->_id = $id;
        $this->_group = $group;

        if ($this->_caching) {
			if ($this->_memoryCache[$group.'-'.$id]) {
				return ($this->_memoryCache[$group.'-'.$id]);
			} else {
				$this->_setFileName($id, $group);
				if ($doNotTestCacheValidity) {
					if (file_exists($this->_file)) {
						$this->_memoryCache[$group.'-'.$id] = $this->_read();
						return ( ($this->_memoryCache[$group.'-'.$id]) );
					}
				} else {
					if (@filemtime($this->_file) > $this->_refreshTime) {
						$this->_memoryCache[$group.'-'.$id] = $this->_read();
						return ( ($this->_memoryCache[$group.'-'.$id]) );
					}
				}
			}
        }
        return false;
    }

    /**
    * Make a file name (with path)
    *
    * @param string $id cache id
    * @param string $group name of the group
    * @access private
    */
    function _setFileName($id, $group)
    {
		//CRC32 with SUBSTR is still faster then MD5.
		$encoded_id = substr(crc32($id),1);
		//$encoded_id = md5($id);
		
		//Generate just the directory, so it can be created.
		//Groups will have there own top level directory, for quick/easy purging of an entire group.
		$dir = $this->_cacheDir.$group.'/'.substr($encoded_id,0,3);
		$this->_create_dir_structure($dir);
		
		$this->_file = $dir.'/'.$encoded_id;
    }

    /**
    * Create full directory structure, Ripped straight from the Smarty Template engine.
	* Version:     2.3.0
	* Copyright:   2001,2002 ispi of Lincoln, Inc.
    *
    * @param string $dir Full directory.
    * @access private
    */
    function _create_dir_structure($dir)
    {
        if (!@file_exists($dir)) {
            $dir_parts = preg_split('!\\'.DIR_SEP.'+!', $dir, -1, PREG_SPLIT_NO_EMPTY);
            $new_dir = ($dir{0} == DIR_SEP) ? DIR_SEP : '';
            foreach ($dir_parts as $dir_part) {
                $new_dir .= $dir_part;
                if (!file_exists($new_dir) && !mkdir($new_dir, 0771)) {
					Cache_Lite::raiseError('Cache_Lite : problem creating directory \"$dir\" !', -3);   
                    return false;
                }
                $new_dir .= DIR_SEP;
            }
        }
    }
}

?>
-------------------------------------------------------------------------
