File

util/promise.lua @ 11405:ce8291e89d67

mod_http_file_share: Remove correct entries when not all expired files were deleted If any of the expired files could not be deleted then we should not forget about that, we should complain loudly and try again. The code got this backwards and would have removed only the entries referring to still existing files. Test procedure: 1. Upload a file 2. chown root:root http_file_share/ 3. In uploads.list, decrease 'when' enough to ensure expiry 4. Reload mod_http_file_share 5. Should see an error in the logs about failure to delete the file 6. Should see that the metadata in uploads.list is still there 7. chown http_file_share/ back to the previous owner 8. Reload mod_http_file_share 9. Should see logs about successful removal of expired file 10. Should see that the metadata in uploads.list is gone 11. Should see that the file was deleted
author Kim Alvefur <zash@zash.se>
date Thu, 25 Feb 2021 23:58:08 +0100
parent 11211:1151140fc757
child 11483:24ce9d380475
line wrap: on
line source

local promise_methods = {};
local promise_mt = { __name = "promise", __index = promise_methods };

local xpcall = require "util.xpcall".xpcall;

function promise_mt:__tostring()
	return  "promise (" .. (self._state or "invalid") .. ")";
end

local function is_promise(o)
	local mt = getmetatable(o);
	return mt == promise_mt;
end

local function wrap_handler(f, resolve, reject, default)
	if not f then
		return default;
	end
	return function (param)
		local ok, ret = xpcall(f, debug.traceback, param);
		if ok then
			resolve(ret);
		else
			reject(ret);
		end
		return true;
	end;
end

local function next_pending(self, on_fulfilled, on_rejected, resolve, reject)
	table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve));
	table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject));
end

local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected
	wrap_handler(on_fulfilled, resolve, reject, resolve)(promise.value);
end

local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled
	wrap_handler(on_rejected, resolve, reject, reject)(promise.reason);
end

local function promise_settle(promise, new_state, new_next, cbs, value)
	if promise._state ~= "pending" then
		return;
	end
	promise._state = new_state;
	promise._next = new_next;
	for _, cb in ipairs(cbs) do
		cb(value);
	end
	-- No need to keep references to callbacks
	promise._pending_on_fulfilled = nil;
	promise._pending_on_rejected = nil;
	return true;
end

local function new_resolve_functions(p)
	local resolved = false;
	local function _resolve(v)
		if resolved then return; end
		resolved = true;
		if is_promise(v) then
			v:next(new_resolve_functions(p));
		elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
			p.value = v;
		end

	end
	local function _reject(e)
		if resolved then return; end
		resolved = true;
		if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
			p.reason = e;
		end
	end
	return _resolve, _reject;
end

local function new(f)
	local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
	if f then
		local resolve, reject = new_resolve_functions(p);
		local ok, ret = xpcall(f, debug.traceback, resolve, reject);
		if not ok and p._state == "pending" then
			reject(ret);
		end
	end
	return p;
end

local function all(promises)
	return new(function (resolve, reject)
		local count, total, results = 0, #promises, {};
		for i = 1, total do
			promises[i]:next(function (v)
				results[i] = v;
				count = count + 1;
				if count == total then
					resolve(results);
				end
			end, reject);
		end
	end);
end

local function all_settled(promises)
	return new(function (resolve)
		local count, total, results = 0, #promises, {};
		for i = 1, total do
			promises[i]:next(function (v)
				results[i] = { status = "fulfilled", value = v };
				count = count + 1;
				if count == total then
					resolve(results);
				end
			end, function (e)
				results[i] = { status = "rejected", reason = e };
				count = count + 1;
				if count == total then
					resolve(results);
				end
			end);
		end
	end);
end

local function race(promises)
	return new(function (resolve, reject)
		for i = 1, #promises do
			promises[i]:next(resolve, reject);
		end
	end);
end

local function resolve(v)
	return new(function (_resolve)
		_resolve(v);
	end);
end

local function reject(v)
	return new(function (_, _reject)
		_reject(v);
	end);
end

local function try(f)
	return resolve():next(function () return f(); end);
end

function promise_methods:next(on_fulfilled, on_rejected)
	return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
		self:_next(on_fulfilled, on_rejected, resolve, reject);
	end);
end

function promise_methods:catch(on_rejected)
	return self:next(nil, on_rejected);
end

function promise_methods:finally(on_finally)
	local function _on_finally(value) on_finally(); return value; end
	local function _on_catch_finally(err) on_finally(); return reject(err); end
	return self:next(_on_finally, _on_catch_finally);
end

return {
	new = new;
	resolve = resolve;
	reject = reject;
	all = all;
	all_settled = all_settled;
	race = race;
	try = try;
	is_promise = is_promise;
}