mediapipe/mediapipe/framework/packet_generator_graph.cc
MediaPipe Team 2b58cceec9 Project import generated by Copybara.
GitOrigin-RevId: d8caa66de45839696f5bd0786ad3bfbcb9cff632
2020-12-09 22:43:33 -05:00

423 lines
16 KiB
C++

// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mediapipe/framework/packet_generator_graph.h"
#include <deque>
#include <functional>
#include <memory>
#include <utility>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/delegating_executor.h"
#include "mediapipe/framework/executor.h"
#include "mediapipe/framework/packet_generator.h"
#include "mediapipe/framework/packet_type.h"
#include "mediapipe/framework/port.h"
#include "mediapipe/framework/port/canonical_errors.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/proto_ns.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status_builder.h"
#include "mediapipe/framework/thread_pool_executor.h"
#include "mediapipe/framework/tool/status_util.h"
namespace mediapipe {
namespace {
// Create the input side packet set for a generator (provided by
// index in the canonical config). unrunnable is set to true if the
// generator cannot be run given the currently available side packets
// (and false otherwise). If an error occurs then unrunnable and
// input_side_packet_set are undefined.
mediapipe::Status CreateInputsForGenerator(
const ValidatedGraphConfig& validated_graph, int generator_index,
const std::map<std::string, Packet>& side_packets,
PacketSet* input_side_packet_set, bool* unrunnable) {
const NodeTypeInfo& node_type_info =
validated_graph.GeneratorInfos()[generator_index];
const auto& generator_name = validated_graph.Config()
.packet_generator(generator_index)
.packet_generator();
// Fill the PacketSet (if possible).
*unrunnable = false;
std::vector<mediapipe::Status> statuses;
for (CollectionItemId id = node_type_info.InputSidePacketTypes().BeginId();
id < node_type_info.InputSidePacketTypes().EndId(); ++id) {
const std::string& name =
node_type_info.InputSidePacketTypes().TagMap()->Names()[id.value()];
std::map<std::string, Packet>::const_iterator it = side_packets.find(name);
if (it == side_packets.end()) {
*unrunnable = true;
continue;
}
input_side_packet_set->Get(id) = it->second;
mediapipe::Status status =
node_type_info.InputSidePacketTypes().Get(id).Validate(
input_side_packet_set->Get(id));
if (!status.ok()) {
statuses.push_back(tool::AddStatusPrefix(
absl::StrCat("Input side packet \"", name,
"\" for PacketGenerator \"", generator_name,
"\" is not of the correct type: "),
status));
}
}
if (!statuses.empty()) {
return tool::CombinedStatus(
absl::StrCat(generator_name, " had invalid configuration."), statuses);
}
return mediapipe::OkStatus();
}
// Generate the packets from a PacketGenerator, place them in
// output_side_packet_set, and validate their types.
mediapipe::Status Generate(const ValidatedGraphConfig& validated_graph,
int generator_index,
const PacketSet& input_side_packet_set,
PacketSet* output_side_packet_set) {
const NodeTypeInfo& node_type_info =
validated_graph.GeneratorInfos()[generator_index];
const PacketGeneratorConfig& generator_config =
validated_graph.Config().packet_generator(generator_index);
const auto& generator_name = generator_config.packet_generator();
ASSIGN_OR_RETURN(
auto static_access,
internal::StaticAccessToGeneratorRegistry::CreateByNameInNamespace(
validated_graph.Package(), generator_name),
_ << generator_name << " is not a valid PacketGenerator.");
MP_RETURN_IF_ERROR(static_access->Generate(generator_config.options(),
input_side_packet_set,
output_side_packet_set))
.SetPrepend()
<< generator_name << "::Generate() failed. ";
MP_RETURN_IF_ERROR(ValidatePacketSet(node_type_info.OutputSidePacketTypes(),
*output_side_packet_set))
.SetPrepend()
<< generator_name
<< "::Generate() output packets were of incorrect type: ";
return mediapipe::OkStatus();
}
// GeneratorScheduler schedules the packet generators in a validated graph for
// execution on an executor.
class GeneratorScheduler {
public:
// If "executor" is null, a DelegatingExecutor will be created internally.
// "initial" must be set to true for the first pass and false for subsequent
// passes. If "initial" is false, non_base_generators contains the non-base
// PacketGenerators (those not run at initialize time due to missing
// dependencies).
GeneratorScheduler(const ValidatedGraphConfig* validated_graph,
mediapipe::Executor* executor,
const std::vector<int>& non_base_generators, bool initial);
// Run a PacketGenerator on a given executor on the provided input
// side packets. After running the generator, schedule any generators
// which became runnable.
void GenerateAndScheduleNext(int generator_index,
std::map<std::string, Packet>* side_packets,
std::unique_ptr<PacketSet> input_side_packet_set)
ABSL_LOCKS_EXCLUDED(mutex_);
// Iterate through all generators in the config, scheduling any that
// are runnable (and haven't been scheduled yet).
void ScheduleAllRunnableGenerators(
std::map<std::string, Packet>* side_packets) ABSL_LOCKS_EXCLUDED(mutex_);
// Waits until there are no pending tasks.
void WaitUntilIdle() ABSL_LOCKS_EXCLUDED(mutex_);
// Stores the indexes of the packet generators that were not scheduled (or
// rather, not executed) in non_scheduled_generators. Returns the combined
// error status if there were errors while running the packet generators.
// NOTE: This method should only be called when there are no pending tasks.
mediapipe::Status GetNonScheduledGenerators(
std::vector<int>* non_scheduled_generators) const;
private:
// Called by delegating_executor_ to add a task.
void AddApplicationThreadTask(std::function<void()> task);
// Run all the application thread tasks (which are kept track of in
// app_thread_tasks_).
void RunApplicationThreadTasks() ABSL_LOCKS_EXCLUDED(app_thread_mutex_);
const ValidatedGraphConfig* const validated_graph_;
mediapipe::Executor* executor_;
mutable absl::Mutex mutex_;
// The number of pending tasks.
int num_tasks_ ABSL_GUARDED_BY(mutex_) = 0;
// This condition variable is signaled when num_tasks_ becomes 0.
absl::CondVar idle_condvar_;
// Accumulates the error statuses while running the packet generators.
std::vector<mediapipe::Status> statuses_ ABSL_GUARDED_BY(mutex_);
// scheduled_generators_[i] is true if the packet generator with index i was
// scheduled (or rather, executed).
std::vector<bool> scheduled_generators_ ABSL_GUARDED_BY(mutex_);
absl::Mutex app_thread_mutex_;
// Tasks to be executed on the application thread.
std::deque<std::function<void()>> app_thread_tasks_
ABSL_GUARDED_BY(app_thread_mutex_);
std::unique_ptr<internal::DelegatingExecutor> delegating_executor_;
};
GeneratorScheduler::GeneratorScheduler(
const ValidatedGraphConfig* validated_graph, mediapipe::Executor* executor,
const std::vector<int>& non_base_generators, bool initial)
: validated_graph_(validated_graph),
executor_(executor),
scheduled_generators_(validated_graph_->Config().packet_generator_size(),
!initial) {
if (!executor_) {
// Run on the application thread.
delegating_executor_ = absl::make_unique<internal::DelegatingExecutor>(
std::bind(&GeneratorScheduler::AddApplicationThreadTask, this,
std::placeholders::_1));
executor_ = delegating_executor_.get();
}
if (!initial) {
// Only schedule the non-base generators.
for (int generator_index : non_base_generators) {
scheduled_generators_[generator_index] = false;
}
}
}
void GeneratorScheduler::GenerateAndScheduleNext(
int generator_index, std::map<std::string, Packet>* side_packets,
std::unique_ptr<PacketSet> input_side_packet_set) {
{
absl::MutexLock lock(&mutex_);
if (!statuses_.empty()) {
// Return early, don't run the generator if we already have errors.
return;
}
}
PacketSet output_side_packet_set(
validated_graph_->GeneratorInfos()[generator_index]
.OutputSidePacketTypes()
.TagMap());
VLOG(1) << "Running generator " << generator_index;
mediapipe::Status status =
Generate(*validated_graph_, generator_index, *input_side_packet_set,
&output_side_packet_set);
{
absl::MutexLock lock(&mutex_);
if (!status.ok()) {
statuses_.push_back(std::move(status));
return;
}
// Add packets to side_packets .
for (CollectionItemId id = output_side_packet_set.BeginId();
id < output_side_packet_set.EndId(); ++id) {
const auto& name = output_side_packet_set.TagMap()->Names()[id.value()];
auto item = side_packets->emplace(name, output_side_packet_set.Get(id));
if (!item.second) {
statuses_.push_back(mediapipe::AlreadyExistsError(
absl::StrCat("Side packet \"", name, "\" was defined twice.")));
}
}
if (!statuses_.empty()) {
return;
}
}
// Check all generators and schedule any that have become runnable.
// TODO Instead of checking all of them, only check ones
// that have input side packets which we have just produced.
ScheduleAllRunnableGenerators(side_packets);
}
void GeneratorScheduler::ScheduleAllRunnableGenerators(
std::map<std::string, Packet>* side_packets) {
absl::MutexLock lock(&mutex_);
const auto& generators = validated_graph_->Config().packet_generator();
for (int index = 0; index < generators.size(); ++index) {
if (scheduled_generators_[index]) {
continue;
}
bool is_unrunnable = false;
// TODO Input side packet set should only be created once.
auto input_side_packet_set =
absl::make_unique<PacketSet>(validated_graph_->GeneratorInfos()[index]
.InputSidePacketTypes()
.TagMap());
mediapipe::Status status =
CreateInputsForGenerator(*validated_graph_, index, *side_packets,
input_side_packet_set.get(), &is_unrunnable);
if (!status.ok()) {
statuses_.push_back(std::move(status));
continue;
}
if (is_unrunnable) {
continue;
}
// The Generator is runnable, schedule a callback to run it.
scheduled_generators_[index] = true;
VLOG(1) << "Scheduling generator " << index;
// Get around the fact that we can't capture a unique_ptr (this
// means a memory leak will result if the lambda is not run).
PacketSet* input_side_packet_set_ptr = input_side_packet_set.release();
++num_tasks_;
mutex_.Unlock();
executor_->Schedule(
[this, index, side_packets, input_side_packet_set_ptr]() {
GenerateAndScheduleNext(
index, side_packets,
std::unique_ptr<PacketSet>(input_side_packet_set_ptr));
{
absl::MutexLock lock(&mutex_);
--num_tasks_;
if (num_tasks_ == 0) {
idle_condvar_.Signal();
}
}
});
mutex_.Lock();
}
}
void GeneratorScheduler::WaitUntilIdle() {
if (executor_ == delegating_executor_.get()) {
// Run the tasks on the application thread.
RunApplicationThreadTasks();
} else {
absl::MutexLock lock(&mutex_);
while (num_tasks_ != 0) {
idle_condvar_.Wait(&mutex_);
}
}
}
mediapipe::Status GeneratorScheduler::GetNonScheduledGenerators(
std::vector<int>* non_scheduled_generators) const {
non_scheduled_generators->clear();
absl::MutexLock lock(&mutex_);
if (!statuses_.empty()) {
return tool::CombinedStatus("PacketGeneratorGraph failed.", statuses_);
}
for (int i = 0; i < scheduled_generators_.size(); ++i) {
if (!scheduled_generators_[i]) {
non_scheduled_generators->push_back(i);
}
}
return mediapipe::OkStatus();
}
void GeneratorScheduler::AddApplicationThreadTask(std::function<void()> task) {
absl::MutexLock lock(&app_thread_mutex_);
app_thread_tasks_.push_back(std::move(task));
}
void GeneratorScheduler::RunApplicationThreadTasks() {
while (true) {
std::function<void()> task_callback;
{
// Get the next task.
absl::MutexLock lock(&app_thread_mutex_);
if (app_thread_tasks_.empty()) {
break;
}
task_callback = std::move(app_thread_tasks_.front());
app_thread_tasks_.pop_front();
}
// Run the next task. Don't hold any lock, since this task could
// schedule further tasks to be run on the application thread.
task_callback();
}
}
} // namespace
PacketGeneratorGraph::~PacketGeneratorGraph() {}
mediapipe::Status PacketGeneratorGraph::Initialize(
const ValidatedGraphConfig* validated_graph, mediapipe::Executor* executor,
const std::map<std::string, Packet>& input_side_packets) {
validated_graph_ = validated_graph;
executor_ = executor;
base_packets_ = input_side_packets;
MP_RETURN_IF_ERROR(
validated_graph_->CanAcceptSidePackets(input_side_packets));
return ExecuteGenerators(&base_packets_, &non_base_generators_,
/*initial=*/true);
}
mediapipe::Status PacketGeneratorGraph::RunGraphSetup(
const std::map<std::string, Packet>& input_side_packets,
std::map<std::string, Packet>* output_side_packets) const {
*output_side_packets = base_packets_;
for (const std::pair<const std::string, Packet>& item : input_side_packets) {
auto iter = output_side_packets->find(item.first);
if (iter != output_side_packets->end()) {
return mediapipe::AlreadyExistsError(
absl::StrCat("Side packet \"", iter->first, "\" was defined twice."));
}
output_side_packets->insert(iter, item);
}
std::vector<int> non_scheduled_generators;
MP_RETURN_IF_ERROR(
validated_graph_->CanAcceptSidePackets(input_side_packets));
// This type check on the required side packets is redundant with
// error checking in ExecuteGenerators, but we do it now to fail early.
MP_RETURN_IF_ERROR(
validated_graph_->ValidateRequiredSidePackets(*output_side_packets));
MP_RETURN_IF_ERROR(ExecuteGenerators(
output_side_packets, &non_scheduled_generators, /*initial=*/false));
RET_CHECK(non_scheduled_generators.empty())
<< "Some Generators were unrunnable (validation should have failed).\n"
"Generator indexes: "
<< absl::StrJoin(non_scheduled_generators, ", ");
return mediapipe::OkStatus();
}
mediapipe::Status PacketGeneratorGraph::ExecuteGenerators(
std::map<std::string, Packet>* output_side_packets,
std::vector<int>* non_scheduled_generators, bool initial) const {
VLOG(1) << "ExecuteGenerators initial == " << initial;
// Iterate through the generators and produce as many output
// side packets as we can. The generators that don't have all the
// required input side packets are put into non_scheduled_generators.
// The ValidatedGraphConfig object is expected to already have sorted
// generators in topological order.
GeneratorScheduler scheduler(validated_graph_, executor_,
non_base_generators_, initial);
scheduler.ScheduleAllRunnableGenerators(output_side_packets);
// Do not return early if scheduler encountered an error. The lambdas
// in the executor must run in order to free resources.
scheduler.WaitUntilIdle();
// It is safe to return now, since all the tasks have run.
return scheduler.GetNonScheduledGenerators(non_scheduled_generators);
}
} // namespace mediapipe