<commit_before><?php

namespace Amp\Cache;

use Amp\Reactor;
use Amp\Success;
use Amp\Failure;

class ArrayCache implements Cache {
    private $sharedState;
    private $ttlWatcherId;

    /**
     * By rebinding the TTL watcher to the shared state object we're
     * able to use __destruct() for "normal" garbage collection of
     * both this instance and the reactor watcher callback. Otherwise
     * this object could only be GC'd when the TTL watcher was cancelled
     * at the event reactor layer.
     */
    public function __construct() {
        $this->sharedState = $sharedState = new \StdClass;
        $sharedState->now = null;
        $sharedState->cache = [];
        $sharedState->cacheTimeouts = [];
        $sharedState->isWatcherEnabled = false;
        $ttlWatcher = function ($watcherId) {
            // xdebug doesn't seem to generate code coverage
            // for this closure ... it's annoying.
            // @codeCoverageIgnoreStart
            $this->now = $now = \time();
            foreach ($this->cacheTimeouts as $key => $expiry) {
                if ($now > $expiry) {
                    unset(
                        $this->cache[$key],
                        $this->cacheTimeouts[$key]
                    );
                } else {
                    break;
                }
            }
            if (empty($this->cacheTimeouts)) {
                \Amp\disable($watcherId);
                $this->isWatcherEnabled = false;
            }
            // @codeCoverageIgnoreEnd
        };
        $ttlWatcher = $ttlWatcher->bind($ttlWatcher, $sharedState);
        $this->ttlWatcherId = \Amp\repeat($ttlWatcher, 1000, $options = [
            "enable" => false,
            "keep_alive" => false,
        ]);
    }

    public function __destruct() {
        $this->sharedState->cache = [];
        $this->sharedState->cacheTimeouts = [];
        \Amp\cancel($this->ttlWatcherId);
    }

    /**
     * {@inheritdoc}
     */
    public function has($key) {
        $exists = isset($this->sharedState->cache[$key]) || array_key_exists($key, $this->sharedState->cache);

        return new Success($exists);
    }

    /**
     * {@inheritdoc}
     */
    public function get($key) {
        return (isset($this->sharedState->cache[$key]) || array_key_exists($key, $this->sharedState->cache))
            ? new Success($this->sharedState->cache[$key])
            : new Failure(new \DomainException(
                "No cache entry exists at key \"{$key}\""
            ));
    }

    /**
     * {@inheritdoc}
     */
    public function set($key, $value, $ttl = null) {
        if (!isset($ttl)) {
            unset($this->sharedState->cacheTimeouts[$key]);
        } elseif (is_int($ttl) && $ttl >= 0) {
            $expiry = $this->sharedState->now + $ttl;
            $this->sharedState->cacheTimeouts[$key] = $expiry;
        } else {
            return new Failure(new \DomainException(
                "Invalid cache TTL; integer >= 0 or null required"
            ));
        }

        $this->sharedState->cache[$key] = $value;
        if ($this->sharedState->cacheTimeouts && !$this->sharedState->isWatcherEnabled) {
            \Amp\enable($this->ttlWatcherId);
            $this->sharedState->now = \time();
        }

        return new Success;
    }
<commit_msg>Fix incorrect cache invalidation expiry timestamps<commit_after><?php

namespace Amp\Cache;

use Amp\Reactor;
use Amp\Success;
use Amp\Failure;

class ArrayCache implements Cache {
    private $sharedState;
    private $ttlWatcherId;

    /**
     * By using a rebound TTL watcher with a shared state object we're
     * able to use __destruct() for "normal" garbage collection of
     * both this instance and the reactor watcher callback. Otherwise
     * this object could only be GC'd when the TTL watcher was cancelled
     * at the event reactor layer.
     */
    public function __construct() {
        $this->sharedState = $sharedState = new \StdClass;
        $sharedState->now = \time();
        $sharedState->cache = [];
        $sharedState->cacheTimeouts = [];
        $ttlWatcher = function ($watcherId) {
            // xdebug doesn't seem to generate code coverage
            // for this closure ... it's annoying.
            // @codeCoverageIgnoreStart
            $this->now = $now = \time();
            foreach ($this->cacheTimeouts as $key => $expiry) {
                if ($now > $expiry) {
                    unset(
                        $this->cache[$key],
                        $this->cacheTimeouts[$key]
                    );
                } else {
                    break;
                }
            }
            // @codeCoverageIgnoreEnd
        };
        $ttlWatcher = $ttlWatcher->bind($ttlWatcher, $sharedState);
        $this->ttlWatcherId = \Amp\repeat($ttlWatcher, 1000, $options = [
            "enable" => true,
            "keep_alive" => false,
        ]);
    }

    public function __destruct() {
        $this->sharedState->cache = [];
        $this->sharedState->cacheTimeouts = [];
        \Amp\cancel($this->ttlWatcherId);
    }

    /**
     * {@inheritdoc}
     */
    public function has($key) {
        $exists = isset($this->sharedState->cache[$key]) || array_key_exists($key, $this->sharedState->cache);

        return new Success($exists);
    }

    /**
     * {@inheritdoc}
     */
    public function get($key) {
        return (isset($this->sharedState->cache[$key]) || array_key_exists($key, $this->sharedState->cache))
            ? new Success($this->sharedState->cache[$key])
            : new Failure(new \DomainException(
                "No cache entry exists at key \"{$key}\""
            ));
    }

    /**
     * {@inheritdoc}
     */
    public function set($key, $value, $ttl = null) {
        if (!isset($ttl)) {
            unset($this->sharedState->cacheTimeouts[$key]);
        } elseif (is_int($ttl) && $ttl >= 0) {
            $expiry = $this->sharedState->now + $ttl;
            $this->sharedState->cacheTimeouts[$key] = $expiry;
        } else {
            return new Failure(new \DomainException(
                "Invalid cache TTL; integer >= 0 or null required"
            ));
        }

        $this->sharedState->cache[$key] = $value;

        return new Success;
    }

{'fraction_non_alphanumeric': 0.1392063492063492, 'fraction_numerical': 0.0019047619047619048, 'mean_word_length': 1.825560538116592, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 91, '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': '6674083', 'n_tokens_mistral': 1836, 'n_tokens_neox': 1761, 'n_words': 403}