1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00

Merge pull request #21660 from TolyaTalamanov:at/handle-exception-in-streamingexecutor

[G-API] Handle exceptions in streaming executor

* Handle exceptions in streaming executor

* Rethrow exception in non-streaming executor

* Clean up

* Put more tests

* Handle exceptions in IE backend

* Handle exception in IE callbacks

* Handle exception in GExecutor

* Handle all exceptions in IE backend

* Not only (std::exception& e)

* Fix comments to review

* Handle input exception in generic way

* Fix comment

* Clean up

* Apply review comments

* Put more comments
* Fix alignment
* Move test outside of HAVE_NGRAPH

* Fix compilation
This commit is contained in:
Anatoliy Talamanov
2022-03-25 11:19:53 +03:00
committed by GitHub
parent 78bc11465b
commit d98e07c3d3
10 changed files with 617 additions and 213 deletions
+22 -1
View File
@@ -270,6 +270,7 @@ class cv::gimpl::GExecutor::Output final: public cv::gimpl::GIslandExecutable::I
{
cv::gimpl::Mag &mag;
std::unordered_map<const void*, int> out_idx;
std::exception_ptr eptr;
GRunArgP get(int idx) override
{
@@ -278,8 +279,18 @@ class cv::gimpl::GExecutor::Output final: public cv::gimpl::GIslandExecutable::I
out_idx[cv::gimpl::proto::ptr(r)] = idx;
return r;
}
void post(GRunArgP&&) override { } // Do nothing here
void post(GRunArgP&&, const std::exception_ptr& e) override
{
if (e)
{
eptr = e;
}
}
void post(EndOfStream&&) override {} // Do nothing here too
void post(Exception&& ex) override
{
eptr = std::move(ex.eptr);
}
void meta(const GRunArgP &out, const GRunArg::Meta &m) override
{
const auto idx = out_idx.at(cv::gimpl::proto::ptr(out));
@@ -291,6 +302,14 @@ public:
{
set(rcs);
}
void verify()
{
if (eptr)
{
std::rethrow_exception(eptr);
}
}
};
void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args)
@@ -389,6 +408,8 @@ void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args)
Input i{m_res, op.in_objects};
Output o{m_res, op.out_objects};
op.isl_exec->run(i, o);
// NB: Check if execution finished without exception.
o.verify();
}
// (7)
+275 -150
View File
@@ -31,6 +31,8 @@
#include <opencv2/gapi/streaming/meta.hpp>
#include <opencv2/gapi/streaming/sync.hpp>
#include <opencv2/gapi/util/variant.hpp>
namespace
{
using namespace cv::gimpl::stream;
@@ -310,14 +312,13 @@ class QueueReader
const std::size_t this_id);
public:
bool getInputVector (std::vector<Q*> &in_queues,
cv::GRunArgs &in_constants,
cv::GRunArgs &isl_inputs);
cv::gimpl::StreamMsg getInputVector (std::vector<Q*> &in_queues,
cv::GRunArgs &in_constants);
bool getResultsVector(std::vector<Q*> &in_queues,
const std::vector<int> &in_mapping,
const std::size_t out_size,
cv::GRunArgs &out_results);
using V = cv::util::variant<cv::GRunArgs, Stop, cv::gimpl::Exception>;
V getResultsVector(std::vector<Q*> &in_queues,
const std::vector<int> &in_mapping,
const std::size_t out_size);
};
void rewindToStop(std::vector<Q*> &in_queues,
@@ -369,9 +370,8 @@ void QueueReader::rewindToStop(std::vector<Q*> &in_queues,
::rewindToStop(in_queues, this_id);
}
bool QueueReader::getInputVector(std::vector<Q*> &in_queues,
cv::GRunArgs &in_constants,
cv::GRunArgs &isl_inputs)
cv::gimpl::StreamMsg QueueReader::getInputVector(std::vector<Q*> &in_queues,
cv::GRunArgs &in_constants)
{
// NB: Need to release resources from the previous step, to fetch new ones.
// On some systems it might be impossible to allocate new memory
@@ -381,72 +381,98 @@ bool QueueReader::getInputVector(std::vector<Q*> &in_queues,
// lifetime, keep the whole cmd vector (of size == # of inputs)
// in memory.
m_cmd.resize(in_queues.size());
isl_inputs.resize(in_queues.size());
cv::GRunArgs isl_inputs(in_queues.size());
cv::optional<cv::gimpl::Exception> exception;
for (auto &&it : ade::util::indexed(in_queues))
{
auto id = ade::util::index(it);
auto &q = ade::util::value(it);
auto id = ade::util::index(it);
auto &q = ade::util::value(it);
if (q == nullptr)
{
GAPI_Assert(!in_constants.empty());
// NULL queue means a graph-constant value (like a
// value-initialized scalar)
// It can also hold a constant value received with
// Stop::Kind::CNST message (see above).
isl_inputs[id] = in_constants[id];
continue;
}
if (q == nullptr)
{
GAPI_Assert(!in_constants.empty());
// NULL queue means a graph-constant value (like a
// value-initialized scalar)
// It can also hold a constant value received with
// Stop::Kind::CNST message (see above).
isl_inputs[id] = in_constants[id];
continue;
}
q->pop(m_cmd[id]);
if (!cv::util::holds_alternative<Stop>(m_cmd[id]))
{
isl_inputs[id] = cv::util::get<cv::GRunArg>(m_cmd[id]);
}
else // A Stop sign
{
const auto &stop = cv::util::get<Stop>(m_cmd[id]);
if (stop.kind == Stop::Kind::CNST)
{
// We've got a Stop signal from a const source,
// propagated as a result of real stream reaching its
// end. Sometimes these signals come earlier than
// real EOS Stops so are deprioritized -- just
// remember the Const value here and continue
// processing other queues. Set queue pointer to
// nullptr and update the const_val vector
// appropriately
m_finishing = true;
in_queues[id] = nullptr;
in_constants.resize(in_queues.size());
in_constants[id] = std::move(stop.cdata);
q->pop(m_cmd[id]);
switch (m_cmd[id].index())
{
case Cmd::index_of<cv::GRunArg>():
isl_inputs[id] = cv::util::get<cv::GRunArg>(m_cmd[id]);
break;
case Cmd::index_of<Stop>():
{
const auto &stop = cv::util::get<Stop>(m_cmd[id]);
if (stop.kind == Stop::Kind::CNST)
{
// We've got a Stop signal from a const source,
// propagated as a result of real stream reaching its
// end. Sometimes these signals come earlier than
// real EOS Stops so are deprioritized -- just
// remember the Const value here and continue
// processing other queues. Set queue pointer to
// nullptr and update the const_val vector
// appropriately
m_finishing = true;
in_queues[id] = nullptr;
in_constants.resize(in_queues.size());
in_constants[id] = std::move(stop.cdata);
// NEXT time (on a next call to getInputVector()), the
// "q==nullptr" check above will be triggered, but now
// we need to make it manually:
isl_inputs[id] = in_constants[id];
}
else
{
GAPI_Assert(stop.kind == Stop::Kind::HARD);
rewindToStop(in_queues, id);
// After queues are read to the proper indicator,
// indicate end-of-stream
return false;
} // if(Cnst)
} // if(Stop)
// NEXT time (on a next call to getInputVector()), the
// "q==nullptr" check above will be triggered, but now
// we need to make it manually:
isl_inputs[id] = in_constants[id];
}
else
{
GAPI_Assert(stop.kind == Stop::Kind::HARD);
rewindToStop(in_queues, id);
// After queues are read to the proper indicator,
// indicate end-of-stream
return cv::gimpl::StreamMsg{cv::gimpl::EndOfStream{}};
} // if(Cnst)
break;
}
case Cmd::index_of<cv::gimpl::Exception>():
{
exception =
cv::util::make_optional(cv::util::get<cv::gimpl::Exception>(m_cmd[id]));
break;
}
default:
GAPI_Assert(false && "Unsupported cmd type in getInputVector()");
}
} // for(in_queues)
if (exception.has_value()) {
return cv::gimpl::StreamMsg{exception.value()};
}
if (m_finishing)
{
// If the process is about to end (a soft Stop was received
// already) and an island has no other inputs than constant
// inputs, its queues may all become nullptrs. Indicate it as
// "no data".
return !ade::util::all_of(in_queues, [](Q *ptr){return ptr == nullptr;});
if (ade::util::all_of(in_queues, [](Q *ptr){return ptr == nullptr;})) {
return cv::gimpl::StreamMsg{cv::gimpl::EndOfStream{}};
}
}
return true; // A regular case - there is data to process.
// A regular case - there is data to process
for (auto& arg : isl_inputs) {
if (arg.index() == cv::GRunArg::index_of<cv::Mat>()) {
arg = cv::GRunArg{ cv::make_rmat<cv::gimpl::RMatOnMat>(cv::util::get<cv::Mat>(arg))
, arg.meta
};
}
}
return cv::gimpl::StreamMsg{std::move(isl_inputs)};
}
// This is a special method to obtain a result vector
@@ -474,33 +500,47 @@ bool QueueReader::getInputVector(std::vector<Q*> &in_queues,
// (_may be_ partially filled) to the same final output queue.
// The receiver part at the GStreamingExecutor level won't change
// because of that.
bool QueueReader::getResultsVector(std::vector<Q*> &in_queues,
const std::vector<int> &in_mapping,
const std::size_t out_size,
cv::GRunArgs &out_results)
QueueReader::V QueueReader::getResultsVector(std::vector<Q*> &in_queues,
const std::vector<int> &in_mapping,
const std::size_t out_size)
{
cv::GRunArgs out_results(out_size);
m_cmd.resize(out_size);
cv::optional<cv::gimpl::Exception> exception;
for (auto &&it : ade::util::indexed(in_queues))
{
auto ii = ade::util::index(it);
auto oi = in_mapping[ii];
auto &q = ade::util::value(it);
q->pop(m_cmd[oi]);
if (!cv::util::holds_alternative<Stop>(m_cmd[oi]))
{
out_results[oi] = std::move(cv::util::get<cv::GRunArg>(m_cmd[oi]));
}
else // A Stop sign
{
// In theory, the CNST should never reach here.
// Collector thread never handles the inputs directly
// (collector's input queues are always produced by
// islands in the graph).
rewindToStop(in_queues, ii);
return false;
} // if(Stop)
switch (m_cmd[oi].index()) {
case Cmd::index_of<cv::GRunArg>():
out_results[oi] = std::move(cv::util::get<cv::GRunArg>(m_cmd[oi]));
break;
case Cmd::index_of<Stop>():
// In theory, the CNST should never reach here.
// Collector thread never handles the inputs directly
// (collector's input queues are always produced by
// islands in the graph).
rewindToStop(in_queues, ii);
return QueueReader::V(Stop{});
case Cmd::index_of<cv::gimpl::Exception>():
exception =
cv::util::make_optional(cv::util::get<cv::gimpl::Exception>(m_cmd[oi]));
break;
default:
cv::util::throw_error(
std::logic_error("Unexpected cmd kind in getResultsVector"));
} // switch
} // for(in_queues)
return true;
if (exception.has_value()) {
return QueueReader::V(exception.value());
}
return QueueReader::V(out_results);
}
@@ -521,7 +561,9 @@ void emitterActorThread(std::shared_ptr<cv::gimpl::GIslandEmitter> emitter,
|| cv::util::holds_alternative<Stop>(cmd));
if (cv::util::holds_alternative<Stop>(cmd))
{
for (auto &&oq : out_queues) oq->push(cmd);
for (auto &&oq : out_queues) {
oq->push(cmd);
}
return;
}
@@ -547,10 +589,21 @@ void emitterActorThread(std::shared_ptr<cv::gimpl::GIslandEmitter> emitter,
// Try to obtain next data chunk from the source
cv::GRunArg data;
const bool result = [&](){
GAPI_ITT_AUTO_TRACE_GUARD(emitter_pull_hndl);
return emitter->pull(data);
}();
bool result = false;
try {
result = [&](){
GAPI_ITT_AUTO_TRACE_GUARD(emitter_pull_hndl);
return emitter->pull(data);
}();
} catch (...) {
auto eptr = std::current_exception();
for (auto &&oq : out_queues)
{
oq->push(Cmd{cv::gimpl::Exception{eptr}});
}
// NB: Go to the next iteration.
continue;
}
if (result)
{
@@ -673,28 +726,8 @@ class StreamingInput final: public cv::gimpl::GIslandExecutable::IInput
std::vector<Q*> &in_queues; // FIXME: This can be part of QueueReader
cv::GRunArgs &in_constants; // FIXME: This can be part of QueueReader
virtual cv::gimpl::StreamMsg get() override
{
GAPI_ITT_STATIC_LOCAL_HANDLE(inputs_get_hndl, "StreamingInput::get");
GAPI_ITT_AUTO_TRACE_GUARD(inputs_get_hndl);
cv::optional<cv::gimpl::StreamMsg> last_read_msg;
cv::GRunArgs isl_input_args;
if (!qr.getInputVector(in_queues, in_constants, isl_input_args))
{
// Stop case
return cv::gimpl::StreamMsg{cv::gimpl::EndOfStream{}};
}
// Wrap all input cv::Mats with RMats
for (auto& arg : isl_input_args) {
if (arg.index() == cv::GRunArg::index_of<cv::Mat>()) {
arg = cv::GRunArg{ cv::make_rmat<cv::gimpl::RMatOnMat>(cv::util::get<cv::Mat>(arg))
, arg.meta
};
}
}
return cv::gimpl::StreamMsg{std::move(isl_input_args)};
}
virtual cv::gimpl::StreamMsg try_get() override
{
// FIXME: This is not very usable at the moment!
@@ -709,17 +742,43 @@ class StreamingInput final: public cv::gimpl::GIslandExecutable::IInput
{
set(in_descs);
}
const cv::gimpl::StreamMsg& read()
{
GAPI_ITT_STATIC_LOCAL_HANDLE(inputs_get_hndl, "StreamingInput::read");
GAPI_ITT_AUTO_TRACE_GUARD(inputs_get_hndl);
last_read_msg =
cv::optional<cv::gimpl::StreamMsg>(
qr.getInputVector(in_queues, in_constants));
return last_read_msg.value();
}
virtual cv::gimpl::StreamMsg get() override
{
GAPI_ITT_STATIC_LOCAL_HANDLE(inputs_get_hndl, "StreamingInput::get");
GAPI_ITT_AUTO_TRACE_GUARD(inputs_get_hndl);
if (!last_read_msg.has_value()) {
(void)read();
}
auto msg = std::move(last_read_msg.value());
last_read_msg = cv::optional<cv::gimpl::StreamMsg>();
return msg;
}
};
class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
{
// These objects form an internal state of the StreamingOutput
struct Posting
{
using V = cv::util::variant<cv::GRunArg, cv::gimpl::EndOfStream>;
V data;
bool ready = false;
};
{
using V = cv::util::variant<cv::GRunArg,
cv::gimpl::EndOfStream,
cv::gimpl::Exception>;
V data;
bool ready = false;
};
using PostingList = std::list<Posting>;
std::vector<PostingList> m_postings;
std::unordered_map< const void*
@@ -820,7 +879,7 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
return ret_val;
}
virtual void post(cv::GRunArgP&& argp) override
virtual void post(cv::GRunArgP&& argp, const std::exception_ptr& exptr) override
{
GAPI_ITT_STATIC_LOCAL_HANDLE(outputs_post_hndl, "StreamingOutput::post");
GAPI_ITT_AUTO_TRACE_GUARD(outputs_post_hndl);
@@ -834,6 +893,9 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
const int out_idx = it->second.first;
const auto out_iter = it->second.second;
out_iter->ready = true;
if (exptr) {
out_iter->data = cv::gimpl::Exception{exptr};
}
m_postIdx.erase(it); // Drop the link from the cache anyway
if (out_iter != m_postings[out_idx].begin())
{
@@ -845,16 +907,22 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
while (post_iter != m_postings[out_idx].end() && post_iter->ready == true)
{
Cmd cmd;
if (cv::util::holds_alternative<cv::GRunArg>(post_iter->data))
switch (post_iter->data.index())
{
cmd = Cmd{cv::util::get<cv::GRunArg>(post_iter->data)};
}
else
{
GAPI_Assert(cv::util::holds_alternative<cv::gimpl::EndOfStream>(post_iter->data));
cmd = Cmd{Stop{}};
m_stops_sent++;
case Posting::V::index_of<cv::GRunArg>():
cmd = Cmd{cv::util::get<cv::GRunArg>(post_iter->data)};
break;
case Posting::V::index_of<cv::gimpl::Exception>():
cmd = Cmd{cv::util::get<cv::gimpl::Exception>(post_iter->data)};
break;
case Posting::V::index_of<cv::gimpl::EndOfStream>():
cmd = Cmd{Stop{}};
m_stops_sent++;
break;
default:
GAPI_Assert(false && "Unreachable code");
}
for (auto &&q : m_out_queues[out_idx])
{
q->push(cmd);
@@ -889,6 +957,7 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput
}
}
}
void meta(const cv::GRunArgP &out, const cv::GRunArg::Meta &m) override
{
std::lock_guard<std::mutex> lock{m_mutex};
@@ -919,6 +988,32 @@ public:
// when it posted/resent all STOP messages to all its outputs.
return m_stops_sent == desc().size();
}
virtual void post(cv::gimpl::Exception&& error) override
{
std::lock_guard<std::mutex> lock{m_mutex};
// If the posting list is empty, just broadcast the stop message.
// If it is not, enqueue the Stop message in the postings list.
for (auto &&it : ade::util::indexed(m_postings))
{
const auto idx = ade::util::index(it);
auto &lst = ade::util::value(it);
if (lst.empty())
{
for (auto &&q : m_out_queues[idx])
{
q->push(Cmd(std::move(error)));
}
}
else
{
Posting p;
p.data = Posting::V{std::move(error)};
p.ready = true;
lst.push_back(std::move(p)); // FIXME: For some reason {}-ctor didn't work here
}
}
}
};
// This thread is a plain dumb processing actor. What it do is just:
@@ -947,7 +1042,17 @@ void islandActorThread(std::vector<cv::gimpl::RcDesc> in_rcs,
while (!output.done())
{
GAPI_ITT_AUTO_TRACE_GUARD(island_hndl);
island_exec->run(input, output);
// NB: In case the input message is an cv::gimpl::Exception
// handle it in a general way.
if (cv::util::holds_alternative<cv::gimpl::Exception>(input.read()))
{
auto in_msg = input.get();
output.post(std::move(cv::util::get<cv::gimpl::Exception>(in_msg)));
}
else
{
island_exec->run(input, output);
}
}
}
@@ -984,26 +1089,33 @@ void collectorThread(std::vector<Q*> in_queues,
while (true)
{
GAPI_ITT_AUTO_TRACE_GUARD(collector_hndl);
cv::GRunArgs this_result(out_size);
const bool ok = [&](){
const auto result = [&](){
GAPI_ITT_AUTO_TRACE_GUARD(collector_get_results_hndl);
return qr.getResultsVector(in_queues, in_mapping, out_size, this_result);
return qr.getResultsVector(in_queues, in_mapping, out_size);
}();
if (!ok)
switch (result.index())
{
if (handle_stop)
case QueueReader::V::index_of<cv::GRunArgs>():
{
out_queue.push(Cmd{Stop{}});
GAPI_ITT_AUTO_TRACE_GUARD(collector_push_hndl);
auto this_result = cv::util::get<cv::GRunArgs>(result);
out_queue.push(Cmd{Result{std::move(this_result), flags}});
break;
}
// Terminate the thread anyway
return;
}
{
GAPI_ITT_AUTO_TRACE_GUARD(collector_push_hndl);
out_queue.push(Cmd{Result{std::move(this_result), flags}});
case QueueReader::V::index_of<Stop>():
if (handle_stop)
{
out_queue.push(Cmd{Stop{}});
}
// Terminate the thread anyway
return;
case QueueReader::V::index_of<cv::gimpl::Exception>():
out_queue.push(Cmd{cv::util::get<cv::gimpl::Exception>(result)});
break;
default:
GAPI_Assert(false && "Unreachable code");
}
}
}
@@ -1707,16 +1819,24 @@ bool cv::gimpl::GStreamingExecutor::pull(cv::GRunArgsP &&outs)
Cmd cmd;
m_out_queue.pop(cmd);
if (cv::util::holds_alternative<Stop>(cmd))
{
wait_shutdown();
return false;
switch (cmd.index()) {
case Cmd::index_of<Stop>():
wait_shutdown();
return false;
case Cmd::index_of<Result>(): {
GAPI_Assert(cv::util::holds_alternative<Result>(cmd));
cv::GRunArgs &this_result = cv::util::get<Result>(cmd).args;
sync_data(this_result, outs);
return true;
}
case Cmd::index_of<Exception>(): {
std::rethrow_exception(cv::util::get<Exception>(cmd).eptr);
return true;
}
default:
GAPI_Assert(false && "Unsupported cmd type in pull");
}
GAPI_Assert(cv::util::holds_alternative<Result>(cmd));
cv::GRunArgs &this_result = cv::util::get<Result>(cmd).args;
sync_data(this_result, outs);
return true;
GAPI_Assert(false && "Unreachable code");
}
bool cv::gimpl::GStreamingExecutor::pull(cv::GOptRunArgsP &&outs)
@@ -1734,15 +1854,20 @@ bool cv::gimpl::GStreamingExecutor::pull(cv::GOptRunArgsP &&outs)
Cmd cmd;
m_out_queue.pop(cmd);
if (cv::util::holds_alternative<Stop>(cmd))
{
wait_shutdown();
return false;
switch (cmd.index()) {
case Cmd::index_of<Stop>():
wait_shutdown();
return false;
case Cmd::index_of<Result>(): {
sync_data(cv::util::get<Result>(cmd), outs);
return true;
}
case Cmd::index_of<Exception>(): {
std::rethrow_exception(cv::util::get<Exception>(cmd).eptr);
return true;
}
}
GAPI_Assert(cv::util::holds_alternative<Result>(cmd));
sync_data(cv::util::get<Result>(cmd), outs);
return true;
GAPI_Assert(false && "Unreachable code");
}
std::tuple<bool, cv::util::variant<cv::GRunArgs, cv::GOptRunArgs>> cv::gimpl::GStreamingExecutor::pull()
@@ -50,11 +50,12 @@ struct Result {
using Cmd = cv::util::variant
< cv::util::monostate
, Start // Tells emitters to start working. Not broadcasted to workers.
, Stop // Tells emitters to stop working. Broadcasted to workers.
, cv::GRunArg // Workers data payload to process.
, Result // Pipeline's data for gout()
>;
, Start // Tells emitters to start working. Not broadcasted to workers.
, Stop // Tells emitters to stop working. Broadcasted to workers.
, cv::GRunArg // Workers data payload to process.
, Result // Pipeline's data for gout()
, cv::gimpl::Exception // Exception which is thrown while execution.
>;
// Interface over a queue. The underlying queue implementation may be
// different. This class is mainly introduced to bring some