hi guys, i just came across boost::outcome[0]. it reminded me the discussion we had back in Barcelona regarding to the error handling in crimson. well, strictly speaking, it's not limited to errors. it covers the non-error handling as well. the question is: shall we start prototyping the crimson variant of outcome<> now? if yes, probably we can leverage boost::outcome<>? a little bit background: seastar uses exception for propagating the error. but it incurs runtime overhead. because, to throw an exception, the libstdc++ runtime needs to acquire a global lock. well, some of us might want to argue, why not just return a future<Result, Error>? let me use an example here, imagine we are handling a write request in OSD. we might need to go through following steps: 1. perform some sanity tests, for instance, to see if the OSD is ready for handling the write request 2. try to read the object info of the object from local storage to see if it already exists 3. write to the object to the local storage, and send write requests to replica OSDs (assuming it's in a replicated pool), wait for the completions of these write ops. 4. update the statistics 5. reply to the client and it's intuitive to structure these steps using chained continuation like do_with(std::move(request), [this](auto request) { return perform_tests(request->object_id).then([request, this] { return read_object_info(request->object_id); }).then([request, this](optional<object_info> object_info) { return when_all( write_local(request->object_id, request->offset, request->data), parallel_for_each(replica_osds, [request](auto replica_osd) { return replica_osd->write_remote(request->object_id, request->offset, request->data) })); }).then([write_size=request.data.size(), this] { update_statistics(write_size); return reply_to(reply_t::success, request); }); }).handle_exception([](auto exception) { return reply_to(reply_t::failure, exception.error_code, request); }); in which, if any test fails in step#1, we either need to wait until the OSD is ready, or just need to bail out, and skip the following steps. the "handle_exception()" clause is used to handle the "bail out" case, where we cannot do anything to serve the request. for instance, the request is invalid. we want to differentiate two types of errors. one of them are actually exceptions which does not happen often in real world, and we don't need/want to optimize for this case. but the other case could be normal. for instance, it's fairly normal that an object does not exist yet, when we are trying to write to it. and we do want to be performant when handling these "errors" in this category, and also, we want to do this in a convenient way just like handling exceptions. because, we need an efficient way to convey the message to caller that "please skip the following continuations, and i would go to this handling route instead". if my memory serves me correctly, we think that we need to create a wrapper around seastar::future<> to allow the caller to do something like // a helper to run func or skip it template<typename Func> auto ignore_on_error(Func&& f) { return [f=std::move(f)](auto&& t) { return t.is_value() ? f(t.value()) ? t; } } return read_object_info(oid).then( return ignore_on_error([](object_info& oi) { return handle_write_with_object_info(std::move(oi)); }).then([](auto t) { return handle_write_without_object_info(); }); ); in the example above, i assume we will do something very different depending on if the object's existence. cheers, --- [0] https://www.boost.org/doc/libs/1_70_0/libs/outcome/doc/html/index.html -- Regards Kefu Chai _______________________________________________ Dev mailing list -- dev@xxxxxxx To unsubscribe send an email to dev-leave@xxxxxxx