Skip to content

Commit

Permalink
Add a DSL word that runs when a specific pool or all pools are idle (#…
Browse files Browse the repository at this point in the history
…100)

Adds a new DSL word Idle that can be used to execute a reaction when the
pool does not have a current task.

There are 4 main variants that this implements

```cpp
on<Idle<>>
```

Will execute when all non always threads are idle

```cpp
on<Idle<Pool<>>
```

Will execute when the default thread pool is idle

```cpp
on<Idle<MainThread>>
```

Will execute when the main thread's pool is idle

```cpp
on<Idle<SomePoolDescriptor>>
```

Will execute when that specific pool is idle

- [x] Fix up the deadlock caused by the recursive mutex acquisition

---------

Co-authored-by: Thomas O'Brien <[email protected]>
Co-authored-by: Tom0Brien <[email protected]>
  • Loading branch information
3 people authored Feb 21, 2024
1 parent f6704a1 commit 1599e91
Show file tree
Hide file tree
Showing 17 changed files with 547 additions and 40 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/gcc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ jobs:
version: "8"
- container: ubuntu:20.04
version: "7"
- container: ubuntu:18.04
version: "6"
- container: ubuntu:18.04
version: "5"

name: Linux GCC-${{ matrix.toolchain.version }}
runs-on: ubuntu-latest
Expand Down
10 changes: 10 additions & 0 deletions src/PowerPlant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ void PowerPlant::submit(std::unique_ptr<threading::ReactionTask>&& task, const b
}
}

void PowerPlant::add_idle_task(const NUClear::id_t& id,
const util::ThreadPoolDescriptor& pool_descriptor,
std::function<void()>&& task) {
scheduler.add_idle_task(id, pool_descriptor, std::move(task));
}

void PowerPlant::remove_idle_task(const NUClear::id_t& id, const util::ThreadPoolDescriptor& pool_descriptor) {
scheduler.remove_idle_task(id, pool_descriptor);
}

void PowerPlant::shutdown() {

// Stop running before we emit the Shutdown event
Expand Down
26 changes: 26 additions & 0 deletions src/PowerPlant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ class PowerPlant {
template <typename T, typename... Args>
T& install(Args&&... args);

/**
* @brief Adds an idle task to the task scheduler.
*
* This function adds an idle task to the task scheduler, which will be executed when the thread pool associated
* with the given `pool_id` has no other tasks to execute. The `task` parameter is a callable object that
* represents the idle task to be executed.
*
* @param id The ID of the task.
* @param pool_descriptor The descriptor for the thread pool to test for idle
* @param task The idle task to be executed.
*/
void add_idle_task(const NUClear::id_t& id,
const util::ThreadPoolDescriptor& pool_descriptor,
std::function<void()>&& task);

/**
* @brief Removes an idle task from the task scheduler.
*
* This function removes an idle task from the task scheduler. The `id` and `pool_id` parameters are used to
* identify the idle task to be removed.
*
* @param id The ID of the task.
* @param pool_descriptor The descriptor for the thread pool to test for idle
*/
void remove_idle_task(const NUClear::id_t& id, const util::ThreadPoolDescriptor& pool_descriptor);

/**
* @brief Generic submit function for submitting tasks to the thread pool.
*
Expand Down
16 changes: 16 additions & 0 deletions src/Reactor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ namespace dsl {

struct Priority;

template <typename>
struct Idle;

struct IO;

struct UDP;
Expand Down Expand Up @@ -190,6 +193,10 @@ class Reactor {
/// @copydoc dsl::word::Once
using Once = dsl::word::Once;

/// @copydoc dsl::word::Idle
template <typename T = void>
using Idle = dsl::word::Idle<T>;

/// @copydoc dsl::word::IO
using IO = dsl::word::IO;

Expand Down Expand Up @@ -227,6 +234,14 @@ class Reactor {
/// @copydoc dsl::word::Shutdown
using Shutdown = dsl::word::Shutdown;

/// @copydoc dsl::word::Pool
template <typename T = void>
using Pool = dsl::word::Pool<T>;

/// @copydoc dsl::word::Group
template <typename T, int I>
using Group = dsl::word::Group<T, I>;

/// @copydoc dsl::word::Every
template <int ticks = 0, class period = std::chrono::milliseconds>
using Every = dsl::word::Every<ticks, period>;
Expand Down Expand Up @@ -426,6 +441,7 @@ class Reactor {
#include "dsl/word/Every.hpp"
#include "dsl/word/Group.hpp"
#include "dsl/word/IO.hpp"
#include "dsl/word/Idle.hpp"
#include "dsl/word/Last.hpp"
#include "dsl/word/MainThread.hpp"
#include "dsl/word/Network.hpp"
Expand Down
2 changes: 1 addition & 1 deletion src/dsl/word/Always.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ namespace dsl {
if (pool_id.count(reaction.id) == 0) {
pool_id[reaction.id] = util::ThreadPoolDescriptor::get_unique_pool_id();
}
return util::ThreadPoolDescriptor{pool_id[reaction.id], 1};
return util::ThreadPoolDescriptor{pool_id[reaction.id], 1, false};
}

template <typename DSL>
Expand Down
89 changes: 89 additions & 0 deletions src/dsl/word/Idle.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 2023 NUClear Contributors
*
* This file is part of the NUClear codebase.
* See https://github.com/Fastcode/NUClear for further info.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#ifndef NUCLEAR_DSL_WORD_IDLE_HPP
#define NUCLEAR_DSL_WORD_IDLE_HPP

#include <map>
#include <mutex>
#include <typeindex>

#include "../../threading/ReactionTask.hpp"
#include "../fusion/NoOp.hpp"
#include "MainThread.hpp"
#include "Pool.hpp"

namespace NUClear {
namespace dsl {
namespace word {

/**
* @brief A base type to handle the common code for idling after turning the pool descriptor into an id.
*/
inline void bind_idle(const std::shared_ptr<threading::Reaction>& reaction,
const util::ThreadPoolDescriptor& pool_descriptor) {

// Our unbinder to remove this reaction
reaction->unbinders.push_back([pool_descriptor](const threading::Reaction& r) { //
r.reactor.powerplant.remove_idle_task(r.id, pool_descriptor);
});

reaction->reactor.powerplant.add_idle_task(reaction->id, pool_descriptor, [reaction] {
reaction->reactor.powerplant.submit(reaction->get_task());
});
}

/**
* @brief Execute a task when there is nothing currently running on the thread pool.
*
* @details
* @code on<Idle<PoolType>>() @endcode
* When the thread pool is idle, this task will be executed. This is use
*
* @par Implements
* Bind
*
* @tparam PoolType the descriptor that was used to create the thread pool
* void for the default pool
* MainThread for the main thread pool
*/
template <typename PoolType>
struct Idle {
template <typename DSL>
static inline void bind(const std::shared_ptr<threading::Reaction>& reaction) {
bind_idle(reaction, PoolType::template pool<DSL>(*reaction));
}
};

template <>
struct Idle<void> {
template <typename DSL>
static inline void bind(const std::shared_ptr<threading::Reaction>& reaction) {
bind_idle(reaction, util::ThreadPoolDescriptor::AllPools());
}
};

} // namespace word
} // namespace dsl
} // namespace NUClear

#endif // NUCLEAR_DSL_WORD_IDLE_HPP
2 changes: 1 addition & 1 deletion src/dsl/word/MainThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace dsl {

template <typename DSL>
static inline util::ThreadPoolDescriptor pool(const threading::Reaction& /*reaction*/) {
return util::ThreadPoolDescriptor{util::ThreadPoolDescriptor::MAIN_THREAD_POOL_ID, 1};
return util::ThreadPoolDescriptor{util::ThreadPoolDescriptor::MAIN_THREAD_POOL_ID, 1, true};
}
};

Expand Down
15 changes: 13 additions & 2 deletions src/dsl/word/Pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ namespace dsl {
* };
* @endcode
*/
template <typename PoolType>
template <typename PoolType = void>
struct Pool {

static_assert(PoolType::thread_count > 0, "Can not have a thread pool with less than 1 thread");
Expand All @@ -84,11 +84,22 @@ namespace dsl {
}
};

// When given void as the pool type we use the default thread pool
template <>
struct Pool<void> {
template <typename DSL>
static inline util::ThreadPoolDescriptor pool(const threading::Reaction& /*reaction*/) {
return util::ThreadPoolDescriptor{};
}
};

// Initialise the thread pool descriptor
template <typename PoolType>
const util::ThreadPoolDescriptor Pool<PoolType>::pool_descriptor = {
util::ThreadPoolDescriptor::get_unique_pool_id(),
PoolType::thread_count};
PoolType::thread_count,
true,
};

} // namespace word
} // namespace dsl
Expand Down
Loading

0 comments on commit 1599e91

Please sign in to comment.