<commit_before><?php
namespace wcf\system\io;
use wcf\system\exception\SystemException;
use wcf\util\FileUtil;

/**
 * AtomicWriter performs an atomic write operation to the given file.
 * Nothing is written to the actual file, until you explicitly call 'flush',
 * simply closing the file will discard any written data.
 * 
 * Only a single 'flush' is supported, the AtomicWriter will be unusable after
 * the data was flushed.
 * 
 * Note: The AtomicWriter only supports a small number of whitelisted (write) operations.
 *
 * @author	<NAME>
 * @copyright	2001-2014 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	com.woltlab.wcf
 * @subpackage	system.io
 * @category	Community Framework
 */
class AtomicWriter extends File {
	/**
	 * The file the data should be flushed into.
	 * @var	string
	 */
	protected $targetFilename = '';
	
	/**
	 * AtomicWriter will be unusable, once this flag is true.
	 * @var	boolean
	 */
	protected $isFlushed = false;
	
	/**
	 * Opens a new file. The file is always opened in binary mode.
	 * 
	 * @param	string		$filename
	 */
	public function __construct($filename) {
		$this->targetFilename = $filename;
		
		$i = 0;
		while (true) {
			try {
				parent::__construct(FileUtil::getTemporaryFilename('atomic_'), 'xb');
				break;
			}
			catch (SystemException $e) {
				// allow at most 5 failures
				if (++$i == 5) {
					throw $e;
				}
			}
		}
		
		if (!flock($this->resource, LOCK_EX)) throw new SystemException('Could not get lock on temporary file');
	}
	
	/**
	 * @see	\wcf\system\io\AtomicWriter::close()
	 */
	public function __destruct() {
		$this->close();
	}
	
	/**
	 * Closes the file, while discarding any written data, noop if the
	 * file is already closed or flushed.
	 */
	public function close() {
		if (!$this->isFlushed) {
			$this->isFlushed = true;
			
			flock($this->resource, LOCK_UN);
			fclose($this->resource);
			@unlink($this->filename);
		}
	}
	
	/**
	 * Persists the written data into the target file. The flush is atomic
	 * if the underlying storage supports an atomic rename.
	 */
	public function flush() {
		$this->isFlushed = true;
		
		fflush($this->resource);
		flock($this->resource, LOCK_UN);
		fclose($this->resource);
		
		rename($this->filename, $this->targetFilename);
	}
	
	/**
	 * @see	\wcf\system\io\File::__call($function, $arguments)
	 */<commit_msg>Fix AtomicWriter race condition on Windows<commit_after><?php
namespace wcf\system\io;
use wcf\system\exception\SystemException;
use wcf\util\FileUtil;

/**
 * AtomicWriter performs an atomic write operation to the given file.
 * Nothing is written to the actual file, until you explicitly call 'flush',
 * simply closing the file will discard any written data.
 * 
 * Only a single 'flush' is supported, the AtomicWriter will be unusable after
 * the data was flushed.
 * 
 * Note: The AtomicWriter only supports a small number of whitelisted (write) operations.
 *
 * @author	<NAME>
 * @copyright	2001-2014 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	com.woltlab.wcf
 * @subpackage	system.io
 * @category	Community Framework
 */
class AtomicWriter extends File {
	/**
	 * The file the data should be flushed into.
	 * @var	string
	 */
	protected $targetFilename = '';
	
	/**
	 * AtomicWriter will be unusable, once this flag is true.
	 * @var	boolean
	 */
	protected $isFlushed = false;
	
	/**
	 * Opens a new file. The file is always opened in binary mode.
	 * 
	 * @param	string		$filename
	 */
	public function __construct($filename) {
		$this->targetFilename = $filename;
		
		$i = 0;
		while (true) {
			try {
				parent::__construct(FileUtil::getTemporaryFilename('atomic_'), 'xb');
				break;
			}
			catch (SystemException $e) {
				// allow at most 5 failures
				if (++$i === 5) {
					throw $e;
				}
			}
		}
		
		if (!flock($this->resource, LOCK_EX)) throw new SystemException('Could not get lock on temporary file');
	}
	
	/**
	 * @see	\wcf\system\io\AtomicWriter::close()
	 */
	public function __destruct() {
		$this->close();
	}
	
	/**
	 * Closes the file, while discarding any written data, noop if the
	 * file is already closed or flushed.
	 */
	public function close() {
		if (!$this->isFlushed) {
			$this->isFlushed = true;
			
			flock($this->resource, LOCK_UN);
			fclose($this->resource);
			@unlink($this->filename);
		}
	}
	
	/**
	 * Persists the written data into the target file. The flush is atomic
	 * if the underlying storage supports an atomic rename.
	 */
	public function flush() {
		$this->isFlushed = true;
		
		fflush($this->resource);
		flock($this->resource, LOCK_UN);
		fclose($this->resource);
		
		$i = 0;
		while (true) {
			try {
				rename($this->filename, $this->targetFilename);
				break;
			}
			catch (SystemException $e) {
				// rename may fail on Windows with a high number
				// of concurrent requests
				// retry up to 5 times with a random sleep
				if (++$i === 5) throw $e;
				
				usleep(mt_rand(0, .1e6)); // 0 to .1 seconds
			}
		}
	}
	
	/**
	 * @see	\wcf\system\io\File::__call($function, $arguments)
	 */
{'fraction_non_alphanumeric': 0.13010648596321395, 'fraction_numerical': 0.005808325266214908, 'mean_word_length': 3.186385737439222, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6200567', 'n_tokens_mistral': 1881, 'n_tokens_neox': 1596, 'n_words': 520}