mirror of
https://github.com/matrix-construct/construct
synced 2025-03-13 21:10:32 +01:00
ircd:Ⓜ️:vm: Add error class; add/checkin options; checkpoint preliminary eval.
This commit is contained in:
parent
14d5bd4f99
commit
bd065a826a
2 changed files with 293 additions and 199 deletions
|
@ -15,19 +15,19 @@
|
|||
///
|
||||
namespace ircd::m::vm
|
||||
{
|
||||
IRCD_M_EXCEPTION(m::error, VM_ERROR, http::INTERNAL_SERVER_ERROR);
|
||||
IRCD_M_EXCEPTION(VM_ERROR, VM_FAULT, http::BAD_REQUEST);
|
||||
IRCD_M_EXCEPTION(VM_ERROR, VM_INVALID, http::BAD_REQUEST);
|
||||
|
||||
enum fault :uint;
|
||||
struct error; // custom exception
|
||||
struct opts;
|
||||
struct eval;
|
||||
|
||||
using fault_t = std::underlying_type<fault>::type;
|
||||
using closure = std::function<void (const event &)>;
|
||||
using closure_bool = std::function<bool (const event &)>;
|
||||
|
||||
extern struct log::log log;
|
||||
extern uint64_t current_sequence;
|
||||
extern ctx::view<const event> inserted;
|
||||
extern const opts default_opts;
|
||||
|
||||
event::id::buf commit(json::iov &event);
|
||||
event::id::buf commit(json::iov &event, const json::iov &content);
|
||||
|
@ -43,44 +43,121 @@ namespace ircd::m::vm
|
|||
///
|
||||
struct ircd::m::vm::eval
|
||||
{
|
||||
struct opts;
|
||||
|
||||
struct opts static const default_opts;
|
||||
|
||||
const struct opts *opts {&default_opts};
|
||||
const vm::opts *opts {&default_opts};
|
||||
db::txn txn{*dbs::events};
|
||||
|
||||
fault operator()(const event &);
|
||||
|
||||
eval(const event &, const struct opts & = default_opts);
|
||||
eval(const struct opts &);
|
||||
eval(const event &, const vm::opts & = default_opts);
|
||||
eval(const vm::opts &);
|
||||
eval() = default;
|
||||
|
||||
friend string_view reflect(const fault &);
|
||||
};
|
||||
|
||||
/// Evaluation Options
|
||||
struct ircd::m::vm::eval::opts
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
/// Individual evaluation state
|
||||
///
|
||||
/// Evaluation faults. These are reasons which evaluation has halted but may
|
||||
/// continue after the user defaults the fault. They are basically types of
|
||||
/// interrupts and traps, which are recoverable. Only the GENERAL protection
|
||||
/// fault (#gp) is an abort and is not recoverable. An exception_ptr will be
|
||||
/// set; aborts are otherwise just thrown as exceptions from eval. If any
|
||||
/// fault isn't handled properly that is an abort.
|
||||
/// interrupts and traps, which are supposed to be recoverable. Only the
|
||||
/// GENERAL protection fault (#gp) is an abort and is not supposed to be
|
||||
/// recoverable. The fault codes have the form of bitflags so they can be
|
||||
/// used in masks; outside of that case only one fault is dealt with at
|
||||
/// a time so they can be switched as they appear in the enum.
|
||||
///
|
||||
enum ircd::m::vm::fault
|
||||
:uint
|
||||
{
|
||||
ACCEPT, ///< No fault.
|
||||
DEBUGSTEP, ///< Debug step. (#db)
|
||||
BREAKPOINT, ///< Debug breakpoint. (#bp)
|
||||
GENERAL, ///< General protection fault. (#gp)
|
||||
EVENT, ///< Eval requires addl events in the ef register (#ef)
|
||||
STATE, ///< Required state is missing (#st)
|
||||
ACCEPT = 0x00, ///< No fault.
|
||||
EXISTS = 0x01, ///< Replaying existing event. (#ex)
|
||||
INVALID = 0x02, ///< Non-conforming event format. (#ud)
|
||||
DEBUGSTEP = 0x04, ///< Debug step. (#db)
|
||||
BREAKPOINT = 0x08, ///< Debug breakpoint. (#bp)
|
||||
GENERAL = 0x10, ///< General protection fault. (#gp)
|
||||
EVENT = 0x20, ///< Eval requires addl events in the ef register (#ef)
|
||||
STATE = 0x40, ///< Required state is missing (#st)
|
||||
};
|
||||
|
||||
/// Evaluation Options
|
||||
struct ircd::m::vm::opts
|
||||
{
|
||||
/// Make writes to database
|
||||
bool write {true};
|
||||
|
||||
/// Apply effects of the eval
|
||||
bool effects {true};
|
||||
|
||||
/// Broadcast to clients/servers
|
||||
bool notify {true};
|
||||
|
||||
/// Mask of conformity failures to allow without error
|
||||
event::conforms non_conform;
|
||||
|
||||
/// Bypass check for event having already been evaluated so it can be
|
||||
/// replayed through the system (not recommended).
|
||||
bool replays {false};
|
||||
|
||||
/// TODO: Y
|
||||
bool prev_check_exists {true};
|
||||
|
||||
/// Mask of faults that are not thrown as exceptions out of eval(). If
|
||||
/// masked, the fault is returned from eval(). By default, the EXISTS
|
||||
/// fault is masked which means existing events won't kill eval loops
|
||||
/// as well as the debug related.
|
||||
fault_t nothrows
|
||||
{
|
||||
EXISTS | DEBUGSTEP | BREAKPOINT
|
||||
};
|
||||
|
||||
/// Mask of faults that are logged to the error facility in vm::log.
|
||||
fault_t errorlog
|
||||
{
|
||||
~(EXISTS | DEBUGSTEP | BREAKPOINT)
|
||||
};
|
||||
|
||||
/// Mask of faults that are logged to the warning facility in vm::log
|
||||
fault_t warnlog
|
||||
{
|
||||
EXISTS
|
||||
};
|
||||
|
||||
/// Whether to log a debug message on successful eval.
|
||||
bool debuglog_accept {false};
|
||||
|
||||
/// Whether to log an info message on successful eval.
|
||||
bool infolog_accept {false};
|
||||
};
|
||||
|
||||
struct ircd::m::vm::error
|
||||
:m::error
|
||||
{
|
||||
vm::fault code;
|
||||
|
||||
template<class... args> error(const fault &code, const char *const &fmt, args&&... a);
|
||||
template<class... args> error(const char *const &fmt, args&&... a);
|
||||
};
|
||||
|
||||
template<class... args>
|
||||
ircd::m::vm::error::error(const fault &code,
|
||||
const char *const &fmt,
|
||||
args&&... a)
|
||||
:m::error
|
||||
{
|
||||
http::NOT_MODIFIED, "M_VM_FAULT", fmt, std::forward<args>(a)...
|
||||
}
|
||||
,code
|
||||
{
|
||||
code
|
||||
}
|
||||
{}
|
||||
|
||||
template<class... args>
|
||||
ircd::m::vm::error::error(const char *const &fmt,
|
||||
args&&... a)
|
||||
:m::error
|
||||
{
|
||||
http::INTERNAL_SERVER_ERROR, "M_VM_FAULT", fmt, std::forward<args>(a)...
|
||||
}
|
||||
,code
|
||||
{
|
||||
fault::GENERAL
|
||||
}
|
||||
{}
|
||||
|
|
357
ircd/m/vm.cc
357
ircd/m/vm.cc
|
@ -16,14 +16,18 @@ ircd::m::vm::log
|
|||
"vm", 'v'
|
||||
};
|
||||
|
||||
ircd::ctx::view<const ircd::m::event>
|
||||
decltype(ircd::m::vm::inserted)
|
||||
ircd::m::vm::inserted
|
||||
{};
|
||||
|
||||
uint64_t
|
||||
decltype(ircd::m::vm::current_sequence)
|
||||
ircd::m::vm::current_sequence
|
||||
{};
|
||||
|
||||
decltype(ircd::m::vm::default_opts)
|
||||
ircd::m::vm::default_opts
|
||||
{};
|
||||
|
||||
/// This function takes an event object vector and adds our origin and event_id
|
||||
/// and hashes and signature and attempts to inject the event into the core.
|
||||
/// The caller is expected to have done their best to check if this event will
|
||||
|
@ -128,6 +132,17 @@ ircd::m::vm::commit(json::iov &event,
|
|||
return commit(event);
|
||||
}
|
||||
|
||||
namespace ircd::m::vm
|
||||
{
|
||||
extern hook::site commit_hook;
|
||||
}
|
||||
|
||||
decltype(ircd::m::vm::commit_hook)
|
||||
ircd::m::vm::commit_hook
|
||||
{
|
||||
{ "name", "vm commit" }
|
||||
};
|
||||
|
||||
/// Insert a new event originating from this server.
|
||||
///
|
||||
/// Figure 1:
|
||||
|
@ -176,9 +191,15 @@ ircd::m::vm::commit(json::iov &iov)
|
|||
vm::current_sequence,
|
||||
pretty_oneline(event));
|
||||
|
||||
//TODO: X
|
||||
vm::opts opts;
|
||||
opts.non_conform |= event::conforms::MISSING_PREV_STATE;
|
||||
|
||||
vm::eval eval{opts};
|
||||
ircd::timer timer;
|
||||
|
||||
eval{event};
|
||||
commit_hook(event);
|
||||
eval(event);
|
||||
|
||||
log.debug("committed event %s (mark: %ld time: %ld$ms)",
|
||||
at<"event_id"_>(event),
|
||||
|
@ -197,25 +218,25 @@ ircd::m::vm::commit(json::iov &iov)
|
|||
|
||||
namespace ircd::m::vm
|
||||
{
|
||||
hook::site notify_hook
|
||||
{
|
||||
{ "name", "vm notify" }
|
||||
};
|
||||
extern hook::site notify_hook;
|
||||
|
||||
void _tmp_effects(const m::event &event); //TODO: X
|
||||
void write(eval &);
|
||||
}
|
||||
|
||||
decltype(ircd::m::vm::eval::default_opts)
|
||||
ircd::m::vm::eval::default_opts
|
||||
{};
|
||||
decltype(ircd::m::vm::notify_hook)
|
||||
ircd::m::vm::notify_hook
|
||||
{
|
||||
{ "name", "vm notify" }
|
||||
};
|
||||
|
||||
ircd::m::vm::eval::eval(const struct opts &opts)
|
||||
ircd::m::vm::eval::eval(const vm::opts &opts)
|
||||
:opts{&opts}
|
||||
{
|
||||
}
|
||||
|
||||
ircd::m::vm::eval::eval(const event &event,
|
||||
const struct opts &opts)
|
||||
const vm::opts &opts)
|
||||
:opts{&opts}
|
||||
{
|
||||
operator()(event);
|
||||
|
@ -225,19 +246,37 @@ enum ircd::m::vm::fault
|
|||
ircd::m::vm::eval::operator()(const event &event)
|
||||
try
|
||||
{
|
||||
const auto &room_id
|
||||
const event::conforms report
|
||||
{
|
||||
at<"room_id"_>(event)
|
||||
event, opts->non_conform.report
|
||||
};
|
||||
|
||||
const auto &event_id
|
||||
if(!report.clean())
|
||||
throw error
|
||||
{
|
||||
fault::INVALID, "Non-conforming event: %s", string(report)
|
||||
};
|
||||
|
||||
const m::event::id &event_id
|
||||
{
|
||||
at<"event_id"_>(event)
|
||||
};
|
||||
|
||||
const m::room::id &room_id
|
||||
{
|
||||
at<"room_id"_>(event)
|
||||
};
|
||||
|
||||
if(!opts->replays && exists(event_id)) //TODO: exclusivity
|
||||
throw error
|
||||
{
|
||||
fault::EXISTS, "Event has already been evaluated."
|
||||
};
|
||||
|
||||
|
||||
const auto &depth
|
||||
{
|
||||
at<"depth"_>(event)
|
||||
json::get<"depth"_>(event)
|
||||
};
|
||||
|
||||
const auto &type
|
||||
|
@ -245,184 +284,122 @@ try
|
|||
unquote(at<"type"_>(event))
|
||||
};
|
||||
|
||||
if(depth < 0)
|
||||
throw VM_INVALID
|
||||
const event::prev prev
|
||||
{
|
||||
event
|
||||
};
|
||||
|
||||
const size_t prev_count
|
||||
{
|
||||
size(json::get<"prev_events"_>(prev))
|
||||
};
|
||||
|
||||
//TODO: ex
|
||||
if(opts->write && prev_count)
|
||||
{
|
||||
for(size_t i(0); i < prev_count; ++i)
|
||||
{
|
||||
"Depth can never be negative"
|
||||
const auto prev_id{prev.prev_event(i)};
|
||||
if(opts->prev_check_exists && !dbs::exists(prev_id))
|
||||
throw error
|
||||
{
|
||||
fault::EVENT, "Missing prev event %s", string_view{prev_id}
|
||||
};
|
||||
}
|
||||
|
||||
uint64_t top;
|
||||
const id::event::buf head
|
||||
{
|
||||
m::head(room_id, top)
|
||||
};
|
||||
|
||||
if(depth == 0 && type != "m.room.create")
|
||||
throw VM_INVALID
|
||||
m::room room{room_id, head};
|
||||
m::room::state state{room};
|
||||
m::state::id_buffer new_root_buf;
|
||||
const auto new_root
|
||||
{
|
||||
"Depth can only be zero for m.room.create types"
|
||||
dbs::write(txn, new_root_buf, state.root_id, event)
|
||||
};
|
||||
|
||||
const event::prev prev{event};
|
||||
const json::array prev_events
|
||||
}
|
||||
else if(opts->write)
|
||||
{
|
||||
json::get<"prev_events"_>(prev)
|
||||
};
|
||||
m::state::id_buffer new_root_buf;
|
||||
const auto new_root
|
||||
{
|
||||
dbs::write(txn, new_root_buf, string_view{}, event)
|
||||
};
|
||||
}
|
||||
|
||||
const json::array prev0
|
||||
if(opts->write)
|
||||
write(*this);
|
||||
|
||||
if(opts->notify)
|
||||
{
|
||||
prev_events[0]
|
||||
};
|
||||
notify_hook(event);
|
||||
vm::inserted.notify(event);
|
||||
}
|
||||
|
||||
const string_view previd
|
||||
{
|
||||
unquote(prev0[0])
|
||||
};
|
||||
if(opts->effects)
|
||||
_tmp_effects(event);
|
||||
|
||||
char old_rootbuf[64];
|
||||
const auto old_root
|
||||
{
|
||||
previd? dbs::state_root(old_rootbuf, previd) : string_view{}
|
||||
};
|
||||
if(opts->debuglog_accept)
|
||||
log.debug("%s", pretty_oneline(event));
|
||||
|
||||
char new_rootbuf[64];
|
||||
const auto new_root
|
||||
{
|
||||
dbs::write(txn, new_rootbuf, old_root, event)
|
||||
};
|
||||
if(opts->infolog_accept)
|
||||
log.info("%s", pretty_oneline(event));
|
||||
|
||||
++cs;
|
||||
log.info("%s", pretty_oneline(event));
|
||||
|
||||
write(*this);
|
||||
return fault::ACCEPT;
|
||||
}
|
||||
catch(const error &e)
|
||||
{
|
||||
log.error("eval %s: %s %s",
|
||||
json::get<"event_id"_>(event),
|
||||
e.what(),
|
||||
e.content);
|
||||
if(opts->errorlog & e.code)
|
||||
log.error("eval %s: %s %s",
|
||||
json::get<"event_id"_>(event),
|
||||
e.what(),
|
||||
e.content);
|
||||
|
||||
if(opts->warnlog & e.code)
|
||||
log.warning("eval %s: %s %s",
|
||||
json::get<"event_id"_>(event),
|
||||
e.what(),
|
||||
e.content);
|
||||
|
||||
if(opts->nothrows & e.code)
|
||||
return e.code;
|
||||
|
||||
throw;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
if(opts->errorlog & fault::GENERAL)
|
||||
log.error("eval %s: #GP: %s",
|
||||
json::get<"event_id"_>(event),
|
||||
e.what());
|
||||
|
||||
if(opts->warnlog & fault::GENERAL)
|
||||
log.warning("eval %s: #GP: %s",
|
||||
json::get<"event_id"_>(event),
|
||||
e.what());
|
||||
|
||||
if(opts->nothrows & fault::GENERAL)
|
||||
return fault::GENERAL;
|
||||
|
||||
throw error
|
||||
{
|
||||
fault::GENERAL, "%s", e.what()
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::vm::write(eval &eval)
|
||||
{
|
||||
log.debug("Committing %zu events to database...",
|
||||
eval.cs);
|
||||
log.debug("Committing %zu cells in %zu bytes to events database...",
|
||||
eval.txn.size(),
|
||||
eval.txn.bytes());
|
||||
|
||||
eval.txn();
|
||||
vm::current_sequence += eval.cs;
|
||||
vm::current_sequence++;
|
||||
eval.txn.clear();
|
||||
eval.cs = 0;
|
||||
}
|
||||
|
||||
ircd::m::vm::front &
|
||||
ircd::m::vm::fronts::get(const room::id &room_id,
|
||||
const event &event)
|
||||
try
|
||||
{
|
||||
front &ret
|
||||
{
|
||||
map[std::string(room_id)]
|
||||
};
|
||||
|
||||
if(ret.map.empty())
|
||||
fetch(room_id, ret, event);
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
map.erase(std::string(room_id));
|
||||
throw;
|
||||
}
|
||||
|
||||
ircd::m::vm::front &
|
||||
ircd::m::vm::fronts::get(const room::id &room_id)
|
||||
{
|
||||
const auto it
|
||||
{
|
||||
map.find(std::string(room_id))
|
||||
};
|
||||
|
||||
if(it == end(map))
|
||||
throw m::NOT_FOUND
|
||||
{
|
||||
"No fronts for unknown room %s", room_id
|
||||
};
|
||||
|
||||
front &ret{it->second};
|
||||
if(unlikely(ret.map.empty()))
|
||||
throw m::NOT_FOUND
|
||||
{
|
||||
"No fronts for room %s", room_id
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ircd::m::vm::front &
|
||||
ircd::m::vm::fetch(const room::id &room_id,
|
||||
front &front,
|
||||
const event &event)
|
||||
{
|
||||
const query<where::equal> query
|
||||
{
|
||||
{ "room_id", room_id },
|
||||
};
|
||||
|
||||
for_each(query, [&front]
|
||||
(const auto &event)
|
||||
{
|
||||
for_each(m::event::prev{event}, [&front, &event]
|
||||
(const auto &key, const auto &prev_events)
|
||||
{
|
||||
for(const json::array &prev_event : prev_events)
|
||||
{
|
||||
const event::id &prev_event_id{unquote(prev_event[0])};
|
||||
front.map.erase(std::string{prev_event_id});
|
||||
}
|
||||
|
||||
const auto &depth
|
||||
{
|
||||
json::get<"depth"_>(event)
|
||||
};
|
||||
|
||||
if(depth > front.top)
|
||||
front.top = depth;
|
||||
|
||||
front.map.emplace(at<"event_id"_>(event), depth);
|
||||
});
|
||||
});
|
||||
|
||||
if(!front.map.empty())
|
||||
return front;
|
||||
|
||||
const event::id &event_id
|
||||
{
|
||||
at<"event_id"_>(event)
|
||||
};
|
||||
|
||||
if(!my_host(room_id.host()))
|
||||
{
|
||||
log.debug("No fronts available for %s; acquiring state eigenvalue at %s...",
|
||||
string_view{room_id},
|
||||
string_view{event_id});
|
||||
|
||||
return front;
|
||||
}
|
||||
|
||||
log.debug("No fronts available for %s using %s",
|
||||
string_view{room_id},
|
||||
string_view{event_id});
|
||||
|
||||
front.map.emplace(at<"event_id"_>(event), json::get<"depth"_>(event));
|
||||
front.top = json::get<"depth"_>(event);
|
||||
|
||||
if(!my_host(room_id.host()))
|
||||
{
|
||||
assert(0);
|
||||
//backfill(room_id, event_id, 64);
|
||||
return front;
|
||||
}
|
||||
|
||||
return front;
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
|
@ -431,11 +408,51 @@ ircd::m::vm::reflect(const enum fault &code)
|
|||
switch(code)
|
||||
{
|
||||
case fault::ACCEPT: return "ACCEPT";
|
||||
case fault::GENERAL: return "GENERAL";
|
||||
case fault::EXISTS: return "EXISTS";
|
||||
case fault::INVALID: return "INVALID";
|
||||
case fault::DEBUGSTEP: return "DEBUGSTEP";
|
||||
case fault::BREAKPOINT: return "BREAKPOINT";
|
||||
case fault::GENERAL: return "GENERAL";
|
||||
case fault::EVENT: return "EVENT";
|
||||
case fault::STATE: return "STATE";
|
||||
}
|
||||
|
||||
return "??????";
|
||||
}
|
||||
|
||||
//TODO: X
|
||||
void
|
||||
ircd::m::vm::_tmp_effects(const m::event &event)
|
||||
{
|
||||
const m::room::id room_id{at<"room_id"_>(event)};
|
||||
const m::user::id sender{at<"sender"_>(event)};
|
||||
const auto &type{at<"type"_>(event)};
|
||||
|
||||
//TODO: X
|
||||
/*
|
||||
if(type == "m.room.create" && my_host(user::id{at<"sender"_>(event)}.host()))
|
||||
{
|
||||
const m::room room{room::id{room_id}};
|
||||
send(room, at<"sender"_>(event), "m.room.power_levels", {}, json::members
|
||||
{
|
||||
{ "users", json::members
|
||||
{
|
||||
{ at<"sender"_>(event), 100L }
|
||||
}}
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
//TODO: X
|
||||
if(type == "m.room.member" && my_host(sender.host()))
|
||||
{
|
||||
m::user::room user_room{sender};
|
||||
send(user_room, sender, "ircd.member", room_id, at<"content"_>(event));
|
||||
}
|
||||
|
||||
//TODO: X
|
||||
if(type == "m.room.join_rules" && my_host(sender.host()))
|
||||
{
|
||||
send(room::id{"!public:zemos.net"}, sender, "ircd.room", room_id, {});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue