0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-05-23 21:33:44 +02:00

ircd:Ⓜ️:vm: Add enumeration for evaluation phases.

This commit is contained in:
Jason Volk 2020-05-11 19:37:55 -07:00
parent a1708a687e
commit 0fd5570c14
10 changed files with 227 additions and 63 deletions

View file

@ -21,6 +21,7 @@ namespace ircd::m::vm
struct copts;
struct eval;
enum fault :uint;
enum phase :uint;
using fault_t = std::underlying_type<fault>::type;
extern const opts default_opts;
@ -30,6 +31,7 @@ namespace ircd::m::vm
extern bool ready;
string_view reflect(const fault &);
string_view reflect(const phase &);
http::code http_code(const fault &);
string_view loghead(const mutable_buffer &, const eval &);
string_view loghead(const eval &); // single tls buffer
@ -88,6 +90,7 @@ struct ircd::m::vm::eval
event::conforms report;
string_view room_version;
const hook::base::site *phase_hook {nullptr};
vm::phase phase {vm::phase(0)};
bool room_internal {false};
static bool for_each_pdu(const std::function<bool (const event &)> &);
@ -149,6 +152,33 @@ enum ircd::m::vm::fault
EVENT = 0x20, ///< Eval requires addl events in the ef register (#ef)
};
/// Evaluation phases
enum ircd::m::vm::phase
:uint
{
NONE = 0x00, ///< No phase; not entered.
DUPCHK = 0x01, ///< Duplicate check & hold.
EXECUTE = 0x02, ///< Execution entered.
ISSUE = 0x03, ///< Issue phase.
CONFORM = 0x04, ///< Conformity phase.
ACCESS = 0x05, ///< Access control phase.
VERIFY = 0x06, ///< Signature verification.
FETCH = 0x07, ///< Fetch phase.
AUTHSTATIC = 0x08, ///< Authentication phase.
PRECOMMIT = 0x09, ///< Precommit sequence.
AUTHRELA = 0x0a, ///< Authentication phase.
COMMIT = 0x0b, ///< Commit sequence.
AUTHPRES = 0x0c, ///< Authentication phase.
EVALUATE = 0x0d, ///< Evaluation phase.
INDEX = 0x0e, ///< Indexing & transaction building phase.
POST = 0x0f, ///< Transaction-included effects phase.
WRITE = 0x10, ///< Write transaction.
RETIRE = 0x11, ///< Retire phase
NOTIFY = 0x12, ///< Notifications phase.
EFFECTS = 0x13, ///< Effects phase.
_NUM_
};
/// Evaluation Options
struct ircd::m::vm::opts
{
@ -161,46 +191,24 @@ struct ircd::m::vm::opts
/// The txnid from the node conducting the eval.
string_view txn_id;
/// Call conform hooks (detailed behavior can be tweaked below)
bool conform {true};
/// Check various access controls before processing event further.
bool access {true};
/// Make fetches or false to bypass fetch stage.
bool fetch {true};
/// Call eval hooks or false to bypass this stage.
bool eval {true};
/// Perform auth or false to bypass this state.
bool auth {true};
/// Make writes to database
bool write {true};
/// Enabled phases of evaluation.
std::bitset<num_of<vm::phase>()> phase {-1UL};
/// Custom write_opts to use during write.
dbs::write_opts wopts;
/// Call post hooks or false to bypass post-write / pre-notify effects.
bool post {true};
/// Broadcast to clients/servers. When true, individual notify options
/// that follow are considered. When false, no notifications occur.
short notify {true};
/// Broadcast to local clients (/sync stream).
bool notify_clients {true};
/// Broadcast to federation servers (/federation/send/).
bool notify_servers {true};
/// Apply effects of this event or false to bypass this stage.
bool effects {true};
/// False to allow a dirty conforms report (not recommended).
bool conforming {true};
/// False to bypass all auth phases.
bool auth {true};
/// Mask of conformity failures to allow without considering dirty.
event::conforms non_conform;
@ -255,9 +263,6 @@ struct ircd::m::vm::opts
/// if the evaluator already performed this and the json source is good.
bool json_source {false};
/// Verify the origin signature; recommended.
bool verify {true};
/// Whether to gather all unknown keys from an input vector of events and
/// perform a parallel/mass fetch before proceeding with the evals.
bool mfetch_keys {true};

View file

@ -331,7 +331,7 @@ ircd::m::commit(const room &room,
opts.non_conform |= event::conforms::MISMATCH_ORIGIN_SENDER;
// Don't need this here
opts.verify = false;
opts.phase.reset(vm::phase::VERIFY);
return vm::eval
{

View file

@ -544,7 +544,7 @@ try
};
vmopts.nothrows = vm::fault::EXISTS;
vmopts.fetch = false;
vmopts.phase.reset(vm::phase::FETCH);
m::vm::eval
{
auth_chain, vmopts
@ -781,7 +781,7 @@ try
vmopts.infolog_accept = true;
vmopts.room_version = room_version;
vmopts.user_id = user_id;
vmopts.fetch = false;
vmopts.phase.reset(vm::phase::FETCH);
vmopts.auth = false;
const vm::eval eval
{

View file

@ -464,7 +464,7 @@ ircd::m::_create_event(const createroom &c)
m::vm::copts opts;
opts.room_version = room_version;
opts.verify = false;
opts.phase.reset(vm::phase::VERIFY);
m::vm::eval
{
event, content, opts

View file

@ -157,6 +157,36 @@ ircd::m::vm::http_code(const fault &code)
}
}
ircd::string_view
ircd::m::vm::reflect(const enum phase &code)
{
switch(code)
{
case phase::NONE: return "NONE";
case phase::DUPCHK: return "DUPCHK";
case phase::EXECUTE: return "EXECUTE";
case phase::ISSUE: return "ISSUE";
case phase::CONFORM: return "CONFORM";
case phase::ACCESS: return "ACCESS";
case phase::VERIFY: return "VERIFY";
case phase::FETCH: return "FETCH";
case phase::AUTHSTATIC: return "AUTHSTATIC";
case phase::PRECOMMIT: return "PRECOMMIT";
case phase::AUTHRELA: return "AUTHRELA";
case phase::COMMIT: return "COMMIT";
case phase::AUTHPRES: return "AUTHPRES";
case phase::EVALUATE: return "EVALUATE";
case phase::INDEX: return "INDEX";
case phase::POST: return "POST";
case phase::WRITE: return "WRITE";
case phase::RETIRE: return "RETIRE";
case phase::NOTIFY: return "NOTIFY";
case phase::EFFECTS: return "EFFECTS";
}
return "??????";
}
ircd::string_view
ircd::m::vm::reflect(const enum fault &code)
{

View file

@ -291,7 +291,7 @@ ircd::m::vm::eval::operator()(const vector_view<m::event> &events)
this->pdus, events
};
if(likely(opts->verify && opts->mfetch_keys))
if(likely(opts->phase[phase::VERIFY] && opts->mfetch_keys))
mfetch_keys();
// Conduct each eval without letting any one exception ruin things for the

View file

@ -128,6 +128,11 @@ try
eval::executing
};
const scope_restore eval_phase
{
eval.phase, phase::EXECUTE
};
const scope_notify notify
{
vm::dock
@ -213,11 +218,18 @@ try
// evaluation is finished. If the other was successful, the exists()
// check will skip this, otherwise we have to try again here because
// this evaluator might be using different options/credentials.
if(likely(opts.unique) && event.event_id)
if(likely(opts.phase[phase::DUPCHK] && opts.unique) && event.event_id)
{
const scope_restore eval_phase
{
eval.phase, phase::DUPCHK
};
sequence::dock.wait([&event]
{
return eval::count(event.event_id) <= 1;
});
}
// We can elide a lot of grief here by not proceeding further and simply
// returning fault::EXISTS after an existence check. If we had to wait for
@ -320,8 +332,15 @@ try
// The issue hook is only called when this server is injecting a newly
// created event.
if(eval.copts && eval.copts->issue)
if(opts.phase[phase::ISSUE] && eval.copts && eval.copts->issue)
{
const scope_restore eval_phase
{
eval.phase, phase::ISSUE
};
call_hook(issue_hook, eval, event, eval);
}
// Branch on whether the event is an EDU or a PDU
const fault ret
@ -344,15 +363,29 @@ try
// The event was executed; now we broadcast the good news. This will
// include notifying client `/sync` and the federation sender.
if(likely(opts.notify))
if(likely(opts.phase[phase::NOTIFY]))
{
const scope_restore eval_phase
{
eval.phase, phase::NOTIFY
};
call_hook(notify_hook, eval, event, eval);
}
// The "effects" of the event are created by listeners on the effect hook.
// These can include the creation of even more events, such as creating a
// PDU out of an EDU, etc. Unlike the post_hook in execute_pdu(), the
// notify for the event at issue here has already been made.
if(likely(opts.effects))
if(likely(opts.phase[phase::EFFECTS]))
{
const scope_restore eval_phase
{
eval.phase, phase::EFFECTS
};
call_hook(effect_hook, eval, event, eval);
}
if(opts.infolog_accept || bool(log_accept_info))
log::info
@ -431,11 +464,25 @@ ircd::m::vm::fault
ircd::m::vm::execute_edu(eval &eval,
const event &event)
{
if(likely(eval.opts->eval))
call_hook(eval_hook, eval, event, eval);
if(likely(eval.opts->phase[phase::EVALUATE]))
{
const scope_restore eval_phase
{
eval.phase, phase::EVALUATE
};
call_hook(eval_hook, eval, event, eval);
}
if(likely(eval.opts->phase[phase::POST]))
{
const scope_restore eval_phase
{
eval.phase, phase::POST
};
if(likely(eval.opts->post))
call_hook(post_hook, eval, event, eval);
}
return fault::ACCEPT;
}
@ -487,8 +534,13 @@ ircd::m::vm::execute_pdu(eval &eval,
// The conform hook runs static checks on an event's formatting and
// composure; these checks only require the event data itself.
if(likely(opts.conform))
if(likely(opts.phase[phase::CONFORM]))
{
const scope_restore eval_phase
{
eval.phase, phase::CONFORM
};
const ctx::critical_assertion ca;
call_hook(conform_hook, eval, event, eval);
}
@ -501,11 +553,18 @@ ircd::m::vm::execute_pdu(eval &eval,
// Wait for any pending duplicate evals before proceeding.
assert(eval::count(event_id));
if(likely(opts.unique))
if(likely(opts.phase[phase::DUPCHK] && opts.unique))
{
const scope_restore eval_phase
{
eval.phase, phase::DUPCHK
};
sequence::dock.wait([&event_id]
{
return eval::count(event_id) <= 1;
});
}
if(likely(opts.unique) && unlikely(eval::count(event_id) > 1))
throw error
@ -519,22 +578,41 @@ ircd::m::vm::execute_pdu(eval &eval,
fault::EXISTS, "Event has already been evaluated."
};
if(likely(opts.access))
call_hook(access_hook, eval, event, eval);
if(likely(opts.phase[phase::ACCESS]))
{
const scope_restore eval_phase
{
eval.phase, phase::ACCESS
};
if(likely(opts.verify) && !verify(event))
call_hook(access_hook, eval, event, eval);
}
if(likely(opts.phase[phase::VERIFY]) && unlikely(!verify(event)))
throw m::BAD_SIGNATURE
{
"Signature verification failed"
"Signature verification failed."
};
// Fetch dependencies
if(likely(opts.fetch))
if(likely(opts.phase[phase::FETCH]))
{
const scope_restore eval_phase
{
eval.phase, phase::FETCH
};
call_hook(fetch_hook, eval, event, eval);
}
// Evaluation by auth system; throws
if(likely(authenticate))
if(likely(opts.phase[phase::AUTHSTATIC]) && authenticate)
{
const scope_restore eval_phase
{
eval.phase, phase::AUTHSTATIC
};
const auto &[pass, fail]
{
room::auth::check_static(event)
@ -564,13 +642,18 @@ ircd::m::vm::execute_pdu(eval &eval,
log, "%s | event sequenced", loghead(eval)
};
const scope_restore eval_phase_precommit
{
eval.phase, phase::PRECOMMIT
};
// Wait until this is the lowest sequence number
sequence::dock.wait([&eval]
{
return eval::seqnext(sequence::uncommitted) == &eval;
});
if(likely(authenticate))
if(likely(opts.phase[phase::AUTHRELA]) && authenticate)
{
const auto &[pass, fail]
{
@ -597,6 +680,11 @@ ircd::m::vm::execute_pdu(eval &eval,
assert(eval::sequnique(sequence::get(eval)));
sequence::uncommitted = sequence::get(eval);
const scope_restore eval_phase_commit
{
eval.phase, phase::COMMIT
};
// Wait until this is the lowest sequence number
sequence::dock.wait([&eval]
{
@ -604,12 +692,26 @@ ircd::m::vm::execute_pdu(eval &eval,
});
// Reevaluation of auth against the present state of the room.
if(likely(authenticate))
if(likely(opts.phase[phase::AUTHPRES]) && authenticate)
{
const scope_restore eval_phase
{
eval.phase, phase::AUTHPRES
};
room::auth::check_present(event);
}
// Evaluation by module hooks
if(likely(opts.eval))
if(likely(opts.phase[phase::EVALUATE]))
{
const scope_restore eval_phase
{
eval.phase, phase::EVALUATE
};
call_hook(eval_hook, eval, event, eval);
}
log::debug
{
@ -620,22 +722,49 @@ ircd::m::vm::execute_pdu(eval &eval,
assert(sequence::retired < sequence::get(eval));
sequence::committed = sequence::get(eval);
write_prepare(eval, event);
write_append(eval, event);
if(likely(opts.phase[phase::INDEX]))
{
const scope_restore eval_phase
{
eval.phase, phase::INDEX
};
write_prepare(eval, event);
write_append(eval, event);
}
// Generate post-eval/pre-notify effects. This function may conduct
// an entire eval of several more events recursively before returning.
if(likely(opts.post))
if(likely(opts.phase[phase::POST]))
{
const scope_restore eval_phase
{
eval.phase, phase::POST
};
call_hook(post_hook, eval, event, eval);
}
// Commit the transaction to database iff this eval is at the stack base.
if(likely(opts.write) && !eval.sequence_shared[0])
if(likely(opts.phase[phase::WRITE]) && !eval.sequence_shared[0])
{
const scope_restore eval_phase
{
eval.phase, phase::WRITE
};
write_commit(eval);
}
// Wait for sequencing only if this is the stack base, otherwise we'll
// never return back to that stack base.
if(likely(!eval.sequence_shared[0]))
{
const scope_restore eval_phase
{
eval.phase, phase::RETIRE
};
sequence::dock.wait([&eval]
{
return eval::seqnext(sequence::retired) == std::addressof(eval);

View file

@ -7968,11 +7968,11 @@ console_cmd__eval(opt &out, const string_view &line)
break;
case "nowrite"_:
opts.write = false;
opts.phase.reset(m::vm::phase::WRITE);
break;
case "noverify"_:
opts.verify = false;
opts.phase.reset(m::vm::phase::VERIFY);
break;
}
});
@ -14909,7 +14909,7 @@ console_cmd__vm(opt &out, const string_view &line)
<< std::right << std::setw(5) << "DONE" << " "
<< std::right << std::setw(9) << "SEQUENCE" << " "
<< std::right << std::setw(9) << "SEQSHARE" << " "
<< std::left << std::setw(9) << "PHASE" << " "
<< std::left << std::setw(10) << "PHASE" << " "
<< std::right << std::setw(6) << "SIZE" << " "
<< std::right << std::setw(5) << "CELLS" << " "
<< std::right << std::setw(8) << "DEPTH" << " "
@ -14942,7 +14942,7 @@ console_cmd__vm(opt &out, const string_view &line)
<< std::right << std::setw(5) << done << " "
<< std::right << std::setw(9) << eval->sequence << " "
<< std::right << std::setw(9) << std::max(eval->sequence_shared[0], eval->sequence_shared[1]) << " "
<< std::left << std::setw(9) << (eval->phase_hook? eval->phase_hook->name() : ""_sv) << " "
<< std::left << std::setw(10) << trunc(reflect(eval->phase), 10) << " "
<< std::right << std::setw(6) << (eval->txn? eval->txn->bytes() : 0UL) << " "
<< std::right << std::setw(5) << (eval->txn? eval->txn->size() : 0UL) << " "
<< std::right << std::setw(8) << (eval->event_ && eval->event_id? long(json::get<"depth"_>(*eval->event_)) : -1L) << " "

View file

@ -153,7 +153,7 @@ put__send_join(client &client,
};
m::vm::opts vmopts;
vmopts.fetch = false;
vmopts.phase.reset(m::vm::phase::FETCH);
m::vm::eval eval
{
event, vmopts

View file

@ -120,7 +120,7 @@ ircd::m::vm::fetch::handle(const event &event,
try
{
assert(eval.opts);
assert(eval.opts->fetch);
assert(eval.opts->phase[phase::FETCH]);
const auto &opts
{
*eval.opts