File

util/cache.lua @ 13461:c673ff1075bd

mod_posix: Move everything to util.startup This allows greater control over the order of events. Notably, the internal ordering between daemonization, initialization of libunbound and setup of signal handling is sensitive. libunbound starts a separate thread for processing DNS requests. If this thread is started before signal handling has been set up, it will not inherit the signal handlers and instead behave as it would have before signal handlers were set up, i.e. cause the whole process to immediately exit. libunbound is usually initialized on the first DNS request, usually triggered by an outgoing s2s connection attempt. If daemonization happens before signals have been set up, signals may not be processed at all.
author Kim Alvefur <zash@zash.se>
date Sat, 23 Mar 2024 20:48:19 +0100
parent 13175:bbdaa770b955
line wrap: on
line source


local function _remove(list, m)
	if m.prev then
		m.prev.next = m.next;
	end
	if m.next then
		m.next.prev = m.prev;
	end
	if list._tail == m then
		list._tail = m.prev;
	end
	if list._head == m then
		list._head = m.next;
	end
	list._count = list._count - 1;
end

local function _insert(list, m)
	if list._head then
		list._head.prev = m;
	end
	m.prev, m.next = nil, list._head;
	list._head = m;
	if not list._tail then
		list._tail = m;
	end
	list._count = list._count + 1;
end

local cache_methods = {};
local cache_mt = { __name = "cache", __index = cache_methods };

function cache_methods:set(k, v)
	local m = self._data[k];
	if m then
		-- Key already exists
		if v ~= nil then
			-- Bump to head of list
			_remove(self, m);
			_insert(self, m);
			m.value = v;
		else
			-- Remove from list
			_remove(self, m);
			self._data[k] = nil;
		end
		return true;
	end
	-- New key
	if v == nil then
		return true;
	end
	-- Check whether we need to remove oldest k/v
	if self._count == self.size then
		local tail = self._tail;
		local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;

		local do_evict = on_evict and on_evict(evicted_key, evicted_value, self);

		if do_evict == false then
			-- Cache is full, and we're not allowed to evict
			return false;
		elseif self._count == self.size then
			-- Cache wasn't grown
			_remove(self, tail);
			self._data[evicted_key] = nil;
		end
	end

	m = { key = k, value = v, prev = nil, next = nil };
	self._data[k] = m;
	_insert(self, m);
	return true;
end

function cache_methods:get(k)
	local m = self._data[k];
	if m then
		return m.value;
	end
	return nil;
end

function cache_methods:items()
	local m = self._head;
	return function ()
		if not m then
			return;
		end
		local k, v = m.key, m.value;
		m = m.next;
		return k, v;
	end
end

function cache_methods:values()
	local m = self._head;
	return function ()
		if not m then
			return;
		end
		local v = m.value;
		m = m.next;
		return v;
	end
end

function cache_methods:count()
	return self._count;
end

function cache_methods:head()
	local head = self._head;
	if not head then return nil, nil; end
	return head.key, head.value;
end

function cache_methods:tail()
	local tail = self._tail;
	if not tail then return nil, nil; end
	return tail.key, tail.value;
end

function cache_methods:resize(new_size)
	new_size = assert(tonumber(new_size), "cache size must be a number");
	new_size = math.floor(new_size);
	assert(new_size > 0, "cache size must be greater than zero");
	local on_evict = self._on_evict;
	while self._count > new_size do
		local tail = self._tail;
		local evicted_key, evicted_value = tail.key, tail.value;
		if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value, self) == false) then
			-- Cache is full, and we're not allowed to evict
			return false;
		end
		_remove(self, tail);
		self._data[evicted_key] = nil;
	end
	self.size = new_size;
	return true;
end

function cache_methods:table()
	--luacheck: ignore 212/t
	if not self.proxy_table then
		self.proxy_table = setmetatable({}, {
			__index = function (t, k)
				return self:get(k);
			end;
			__newindex = function (t, k, v)
				if not self:set(k, v) then
					error("failed to insert key into cache - full");
				end
			end;
			__pairs = function (t)
				return self:items();
			end;
			__len = function (t)
				return self:count();
			end;
		});
	end
	return self.proxy_table;
end

function cache_methods:clear()
	self._data = {};
	self._count = 0;
	self._head = nil;
	self._tail = nil;
end

local function new(size, on_evict)
	size = assert(tonumber(size), "cache size must be a number");
	size = math.floor(size);
	assert(size > 0, "cache size must be greater than zero");
	local data = {};
	return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt);
end

return {
	new = new;
}