mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
initial commit
This commit is contained in:
commit
c2da007f40
1610 changed files with 398047 additions and 0 deletions
11
tdactor/.gitrepo
Normal file
11
tdactor/.gitrepo
Normal file
|
@ -0,0 +1,11 @@
|
|||
; DO NOT EDIT (unless you know what you are doing)
|
||||
;
|
||||
; This subdirectory is a git "subrepo", and this file is maintained by the
|
||||
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
|
||||
;
|
||||
[subrepo]
|
||||
remote = git@github.com:arseny30/tdactor.git
|
||||
branch = master
|
||||
commit = 023418469da909416b067660cc44bb4d9e9a4739
|
||||
parent = da0d81f9227112f21b00dfcd060e19b44f12f1f8
|
||||
cmdver = 0.3.1
|
62
tdactor/CMakeLists.txt
Normal file
62
tdactor/CMakeLists.txt
Normal file
|
@ -0,0 +1,62 @@
|
|||
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
|
||||
#SOURCE SETS
|
||||
set(TDACTOR_SOURCE
|
||||
td/actor/core/ActorExecutor.cpp
|
||||
td/actor/core/CpuWorker.cpp
|
||||
td/actor/core/IoWorker.cpp
|
||||
td/actor/core/Scheduler.cpp
|
||||
|
||||
td/actor/MultiPromise.cpp
|
||||
|
||||
td/actor/actor.h
|
||||
td/actor/ActorId.h
|
||||
td/actor/ActorOwn.h
|
||||
td/actor/ActorShared.h
|
||||
td/actor/common.h
|
||||
td/actor/PromiseFuture.h
|
||||
td/actor/MultiPromise.h
|
||||
|
||||
td/actor/core/Actor.h
|
||||
td/actor/core/ActorExecuteContext.h
|
||||
td/actor/core/ActorExecutor.h
|
||||
td/actor/core/ActorInfo.h
|
||||
td/actor/core/ActorInfoCreator.h
|
||||
td/actor/core/ActorLocker.h
|
||||
td/actor/core/ActorMailbox.h
|
||||
td/actor/core/ActorMessage.h
|
||||
td/actor/core/ActorSignals.h
|
||||
td/actor/core/ActorState.h
|
||||
td/actor/core/CpuWorker.h
|
||||
td/actor/core/Context.h
|
||||
td/actor/core/IoWorker.h
|
||||
td/actor/core/Scheduler.h
|
||||
td/actor/core/SchedulerContext.h
|
||||
td/actor/core/SchedulerId.h
|
||||
td/actor/core/SchedulerMessage.h
|
||||
)
|
||||
|
||||
set(TDACTOR_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_promise.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_core.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
#RULES
|
||||
|
||||
#LIBRARIES
|
||||
|
||||
add_library(tdactor STATIC ${TDACTOR_SOURCE})
|
||||
target_include_directories(tdactor PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(tdactor PUBLIC tdutils)
|
||||
|
||||
# BEGIN-INTERNAL
|
||||
add_subdirectory(benchmark)
|
||||
|
||||
# END-INTERNAL
|
||||
install(TARGETS tdactor EXPORT TdTargets
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
INCLUDES DESTINATION include
|
||||
)
|
19
tdactor/benchmark/CMakeLists.txt
Normal file
19
tdactor/benchmark/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
|
||||
|
||||
set(BENCHMARK_SOURCE
|
||||
benchmark.cpp
|
||||
third_party/mp-queue.c
|
||||
|
||||
third_party/FAAArrayQueue.h
|
||||
third_party/LCRQueue.h
|
||||
third_party/LazyIndexArrayQueue.h
|
||||
third_party/MoodyCamelQueue.h
|
||||
)
|
||||
add_executable(benchmark ${BENCHMARK_SOURCE})
|
||||
target_include_directories(benchmark PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(benchmark PRIVATE tdactor)
|
||||
|
||||
if (MSVC)
|
||||
set_property(SOURCE benchmark.cpp APPEND_STRING PROPERTY COMPILE_FLAGS " /wd4457 /wd4316")
|
||||
endif()
|
||||
|
1202
tdactor/benchmark/benchmark.cpp
Normal file
1202
tdactor/benchmark/benchmark.cpp
Normal file
File diff suppressed because it is too large
Load diff
371
tdactor/benchmark/third_party/FAAArrayQueue.h
vendored
Normal file
371
tdactor/benchmark/third_party/FAAArrayQueue.h
vendored
Normal file
|
@ -0,0 +1,371 @@
|
|||
/******************************************************************************
|
||||
* Copyright (c) 2014-2016, Pedro Ramalhete, Andreia Correia
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Concurrency Freaks nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef _FAA_ARRAY_QUEUE_HP_H_
|
||||
#define _FAA_ARRAY_QUEUE_HP_H_
|
||||
|
||||
#include "HazardPointers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ConcurrencyFreaks {
|
||||
/**
|
||||
* <h1> Fetch-And-Add Array Queue </h1>
|
||||
*
|
||||
* Each node has one array but we don't search for a vacant entry. Instead, we
|
||||
* use FAA to obtain an index in the array, for enqueueing or dequeuing.
|
||||
*
|
||||
* There are some similarities between this queue and the basic queue in YMC:
|
||||
* http://chaoran.me/assets/pdf/wfq-ppopp16.pdf
|
||||
* but it's not the same because the queue in listing 1 is obstruction-free, while
|
||||
* our algorithm is lock-free.
|
||||
* In FAAArrayQueue eventually a new node will be inserted (using Michael-Scott's
|
||||
* algorithm) and it will have an item pre-filled in the first position, which means
|
||||
* that at most, after BUFFER_SIZE steps, one item will be enqueued (and it can then
|
||||
* be dequeued). This kind of progress is lock-free.
|
||||
*
|
||||
* Each entry in the array may contain one of three possible values:
|
||||
* - A valid item that has been enqueued;
|
||||
* - nullptr, which means no item has yet been enqueued in that position;
|
||||
* - taken, a special value that means there was an item but it has been dequeued;
|
||||
*
|
||||
* Enqueue algorithm: FAA + CAS(null,item)
|
||||
* Dequeue algorithm: FAA + CAS(item,taken)
|
||||
* Consistency: Linearizable
|
||||
* enqueue() progress: lock-free
|
||||
* dequeue() progress: lock-free
|
||||
* Memory Reclamation: Hazard Pointers (lock-free)
|
||||
* Uncontended enqueue: 1 FAA + 1 CAS + 1 HP
|
||||
* Uncontended dequeue: 1 FAA + 1 CAS + 1 HP
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Lock-Free Linked List as described in Maged Michael and Michael Scott's paper:
|
||||
* {@link http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf}
|
||||
* <a href="http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf">
|
||||
* Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms</a>
|
||||
* <p>
|
||||
* The paper on Hazard Pointers is named "Hazard Pointers: Safe Memory
|
||||
* Reclamation for Lock-Free objects" and it is available here:
|
||||
* http://web.cecs.pdx.edu/~walpole/class/cs510/papers/11.pdf
|
||||
*
|
||||
* @author Pedro Ramalhete
|
||||
* @author Andreia Correia
|
||||
*/
|
||||
template <typename T>
|
||||
class FAAArrayQueue {
|
||||
static const long BUFFER_SIZE = 1024; // 1024
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
std::atomic<int> deqidx;
|
||||
std::atomic<T*> items[BUFFER_SIZE];
|
||||
std::atomic<int> enqidx;
|
||||
std::atomic<Node*> next;
|
||||
|
||||
// Start with the first entry pre-filled and enqidx at 1
|
||||
Node(T* item) : deqidx{0}, enqidx{1}, next{nullptr} {
|
||||
items[0].store(item, std::memory_order_relaxed);
|
||||
for (long i = 1; i < BUFFER_SIZE; i++) {
|
||||
items[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
bool casNext(Node* cmp, Node* val) {
|
||||
return next.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
};
|
||||
|
||||
bool casTail(Node* cmp, Node* val) {
|
||||
return tail.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
|
||||
bool casHead(Node* cmp, Node* val) {
|
||||
return head.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
|
||||
// Pointers to head and tail of the list
|
||||
alignas(128) std::atomic<Node*> head;
|
||||
alignas(128) std::atomic<Node*> tail;
|
||||
|
||||
static const int MAX_THREADS = 128;
|
||||
const int maxThreads;
|
||||
|
||||
T* taken = (T*)new int(); // Muuuahahah !
|
||||
|
||||
// We need just one hazard pointer
|
||||
HazardPointers<Node> hp{1, maxThreads};
|
||||
const int kHpTail = 0;
|
||||
const int kHpHead = 0;
|
||||
|
||||
public:
|
||||
FAAArrayQueue(int maxThreads = MAX_THREADS) : maxThreads{maxThreads} {
|
||||
Node* sentinelNode = new Node(nullptr);
|
||||
sentinelNode->enqidx.store(0, std::memory_order_relaxed);
|
||||
head.store(sentinelNode, std::memory_order_relaxed);
|
||||
tail.store(sentinelNode, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
~FAAArrayQueue() {
|
||||
while (dequeue(0) != nullptr)
|
||||
; // Drain the queue
|
||||
delete head.load(); // Delete the last node
|
||||
delete (int*)taken;
|
||||
}
|
||||
|
||||
std::string className() {
|
||||
return "FAAArrayQueue";
|
||||
}
|
||||
|
||||
void enqueue(T* item, const int tid) {
|
||||
while (true) {
|
||||
Node* ltail = hp.protect(kHpTail, tail, tid);
|
||||
const int idx = ltail->enqidx.fetch_add(1);
|
||||
if (idx > BUFFER_SIZE - 1) { // This node is full
|
||||
if (ltail != tail.load())
|
||||
continue;
|
||||
Node* lnext = ltail->next.load();
|
||||
if (lnext == nullptr) {
|
||||
Node* newNode = new Node(item);
|
||||
if (ltail->casNext(nullptr, newNode)) {
|
||||
casTail(ltail, newNode);
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
delete newNode;
|
||||
} else {
|
||||
casTail(ltail, lnext);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
T* itemnull = nullptr;
|
||||
if (ltail->items[idx].compare_exchange_strong(itemnull, item)) {
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T* dequeue(const int tid) {
|
||||
while (true) {
|
||||
Node* lhead = hp.protect(kHpHead, head, tid);
|
||||
if (lhead->deqidx.load() >= lhead->enqidx.load() && lhead->next.load() == nullptr)
|
||||
break;
|
||||
const int idx = lhead->deqidx.fetch_add(1);
|
||||
if (idx > BUFFER_SIZE - 1) { // This node has been drained, check if there is another one
|
||||
Node* lnext = lhead->next.load();
|
||||
if (lnext == nullptr)
|
||||
break; // No more nodes in the queue
|
||||
if (casHead(lhead, lnext))
|
||||
hp.retire(lhead, tid);
|
||||
continue;
|
||||
}
|
||||
T* item = lhead->items[idx].exchange(taken);
|
||||
if (item == nullptr)
|
||||
continue;
|
||||
hp.clear(tid);
|
||||
return item;
|
||||
}
|
||||
hp.clear(tid);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* <h1> Lazy Index Array Queue </h1>
|
||||
*
|
||||
* Same as Linear Array Queue but with lazy indexes for both enqueuers and dequeuers.
|
||||
*
|
||||
* This is a lock-free queue where each node contains an array of items.
|
||||
* Each entry in the array may contain on of three possible values:
|
||||
* - A valid item that has been enqueued;
|
||||
* - nullptr, which means no item has yet been enqueued in that position;
|
||||
* - taken, a special value that means there was an item but it has been dequeued;
|
||||
* The enqueue() searches for the first nullptr entry in the array and tries
|
||||
* to CAS from nullptr to its item.
|
||||
* The dequeue() searches for the first valid item in the array and tries to
|
||||
* CAS from item to "taken".
|
||||
*
|
||||
* Enqueue algorithm: Linear array search starting at lazy index with CAS(nullptr,item)
|
||||
* Dequeue algorithm: Linear array search starting at lazy index with CAS(item,taken)
|
||||
* Consistency: Linearizable
|
||||
* enqueue() progress: lock-free
|
||||
* dequeue() progress: lock-free
|
||||
* Memory Reclamation: Hazard Pointers (lock-free)
|
||||
* Uncontended enqueue: 1 CAS + 1 HP
|
||||
* Uncontended dequeue: 1 CAS + 1 HP
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Lock-Free Linked List as described in Maged Michael and Michael Scott's paper:
|
||||
* {@link http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf}
|
||||
* <a href="http://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf">
|
||||
* Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms</a>
|
||||
* <p>
|
||||
* The paper on Hazard Pointers is named "Hazard Pointers: Safe Memory
|
||||
* Reclamation for Lock-Free objects" and it is available here:
|
||||
* http://web.cecs.pdx.edu/~walpole/class/cs510/papers/11.pdf
|
||||
*
|
||||
* @author Pedro Ramalhete
|
||||
* @author Andreia Correia
|
||||
*/
|
||||
template <typename T>
|
||||
class LazyIndexArrayQueue {
|
||||
static const long BUFFER_SIZE = 1024;
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
std::atomic<int> deqidx;
|
||||
std::atomic<T*> items[BUFFER_SIZE];
|
||||
std::atomic<int> enqidx;
|
||||
std::atomic<Node*> next;
|
||||
|
||||
Node(T* item) : deqidx{0}, enqidx{0}, next{nullptr} {
|
||||
items[0].store(item, std::memory_order_relaxed);
|
||||
for (int i = 1; i < BUFFER_SIZE; i++) {
|
||||
items[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
bool casNext(Node* cmp, Node* val) {
|
||||
return next.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
};
|
||||
|
||||
bool casTail(Node* cmp, Node* val) {
|
||||
return tail.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
|
||||
bool casHead(Node* cmp, Node* val) {
|
||||
return head.compare_exchange_strong(cmp, val);
|
||||
}
|
||||
|
||||
// Pointers to head and tail of the list
|
||||
alignas(128) std::atomic<Node*> head;
|
||||
alignas(128) std::atomic<Node*> tail;
|
||||
|
||||
static const int MAX_THREADS = 128;
|
||||
const int maxThreads;
|
||||
|
||||
T* taken = (T*)new int(); // Muuuahahah !
|
||||
|
||||
// We need just one hazard pointer
|
||||
HazardPointers<Node> hp{1, maxThreads};
|
||||
const int kHpTail = 0;
|
||||
const int kHpHead = 0;
|
||||
|
||||
public:
|
||||
LazyIndexArrayQueue(int maxThreads = MAX_THREADS) : maxThreads{maxThreads} {
|
||||
Node* sentinelNode = new Node(nullptr);
|
||||
head.store(sentinelNode, std::memory_order_relaxed);
|
||||
tail.store(sentinelNode, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
~LazyIndexArrayQueue() {
|
||||
while (dequeue(0) != nullptr)
|
||||
; // Drain the queue
|
||||
delete head.load(); // Delete the last node
|
||||
delete (int*)taken;
|
||||
}
|
||||
|
||||
std::string className() {
|
||||
return "LazyIndexArrayQueue";
|
||||
}
|
||||
|
||||
void enqueue(T* item, const int tid) {
|
||||
while (true) {
|
||||
Node* ltail = hp.protect(kHpTail, tail, tid);
|
||||
if (ltail->items[BUFFER_SIZE - 1].load() != nullptr) { // This node is full
|
||||
if (ltail != tail.load())
|
||||
continue;
|
||||
Node* lnext = ltail->next.load();
|
||||
if (lnext == nullptr) {
|
||||
Node* newNode = new Node(item);
|
||||
if (ltail->casNext(nullptr, newNode)) {
|
||||
casTail(ltail, newNode);
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
delete newNode;
|
||||
} else {
|
||||
casTail(ltail, lnext);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Find the first null entry in items[] and try to CAS from null to item
|
||||
for (int i = ltail->enqidx.load(); i < BUFFER_SIZE; i++) {
|
||||
if (ltail->items[i].load() != nullptr)
|
||||
continue;
|
||||
T* itemnull = nullptr;
|
||||
if (ltail->items[i].compare_exchange_strong(itemnull, item)) {
|
||||
ltail->enqidx.store(i + 1, std::memory_order_release);
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
if (ltail != tail.load())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T* dequeue(const int tid) {
|
||||
while (true) {
|
||||
Node* lhead = hp.protect(kHpHead, head, tid);
|
||||
if (lhead->items[BUFFER_SIZE - 1].load() == taken) { // This node has been drained, check if there is another one
|
||||
Node* lnext = lhead->next.load();
|
||||
if (lnext == nullptr) { // No more nodes in the queue
|
||||
hp.clear(tid);
|
||||
return nullptr;
|
||||
}
|
||||
if (casHead(lhead, lnext))
|
||||
hp.retire(lhead, tid);
|
||||
continue;
|
||||
}
|
||||
// Find the first non taken entry in items[] and try to CAS from item to taken
|
||||
for (int i = lhead->deqidx.load(); i < BUFFER_SIZE; i++) {
|
||||
T* item = lhead->items[i].load();
|
||||
if (item == nullptr) {
|
||||
hp.clear(tid);
|
||||
return nullptr; // This node is empty
|
||||
}
|
||||
if (item == taken)
|
||||
continue;
|
||||
if (lhead->items[i].compare_exchange_strong(item, taken)) {
|
||||
lhead->deqidx.store(i + 1, std::memory_order_release);
|
||||
hp.clear(tid);
|
||||
return item;
|
||||
}
|
||||
if (lhead != head.load())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace ConcurrencyFreaks
|
||||
|
||||
#endif /* _FAA_ARRAY_QUEUE_HP_H_ */
|
152
tdactor/benchmark/third_party/HazardPointers.h
vendored
Normal file
152
tdactor/benchmark/third_party/HazardPointers.h
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
/******************************************************************************
|
||||
* Copyright (c) 2014-2016, Pedro Ramalhete, Andreia Correia
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Concurrency Freaks nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef _HAZARD_POINTERS_H_
|
||||
#define _HAZARD_POINTERS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace ConcurrencyFreaks {
|
||||
template <typename T>
|
||||
class HazardPointers {
|
||||
private:
|
||||
static const int HP_MAX_THREADS = 128;
|
||||
static const int HP_MAX_HPS = 4; // This is named 'K' in the HP paper
|
||||
static const int CLPAD = 128 / sizeof(std::atomic<T*>);
|
||||
static const int HP_THRESHOLD_R = 0; // This is named 'R' in the HP paper
|
||||
static const int MAX_RETIRED = HP_MAX_THREADS * HP_MAX_HPS; // Maximum number of retired objects per thread
|
||||
|
||||
const int maxHPs;
|
||||
const int maxThreads;
|
||||
|
||||
std::atomic<T*>* hp[HP_MAX_THREADS];
|
||||
// It's not nice that we have a lot of empty vectors, but we need padding to avoid false sharing
|
||||
std::vector<T*> retiredList[HP_MAX_THREADS * CLPAD];
|
||||
|
||||
public:
|
||||
HazardPointers(int maxHPs = HP_MAX_HPS, int maxThreads = HP_MAX_THREADS) : maxHPs{maxHPs}, maxThreads{maxThreads} {
|
||||
for (int ithread = 0; ithread < HP_MAX_THREADS; ithread++) {
|
||||
hp[ithread] =
|
||||
new std::atomic<T*>[CLPAD *
|
||||
2]; // We allocate four cache lines to allow for many hps and without false sharing
|
||||
for (int ihp = 0; ihp < HP_MAX_HPS; ihp++) {
|
||||
hp[ithread][ihp].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~HazardPointers() {
|
||||
for (int ithread = 0; ithread < HP_MAX_THREADS; ithread++) {
|
||||
delete[] hp[ithread];
|
||||
// Clear the current retired nodes
|
||||
for (unsigned iret = 0; iret < retiredList[ithread * CLPAD].size(); iret++) {
|
||||
delete retiredList[ithread * CLPAD][iret];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress Condition: wait-free bounded (by maxHPs)
|
||||
*/
|
||||
void clear(const int tid) {
|
||||
for (int ihp = 0; ihp < maxHPs; ihp++) {
|
||||
hp[tid][ihp].store(nullptr, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress Condition: wait-free population oblivious
|
||||
*/
|
||||
void clearOne(int ihp, const int tid) {
|
||||
hp[tid][ihp].store(nullptr, std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress Condition: lock-free
|
||||
*/
|
||||
T* protect(int index, const std::atomic<T*>& atom, const int tid) {
|
||||
T* n = nullptr;
|
||||
T* ret;
|
||||
while ((ret = atom.load()) != n) {
|
||||
hp[tid][index].store(ret);
|
||||
n = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the same value that is passed as ptr, which is sometimes useful
|
||||
* Progress Condition: wait-free population oblivious
|
||||
*/
|
||||
T* protectPtr(int index, T* ptr, const int tid) {
|
||||
hp[tid][index].store(ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the same value that is passed as ptr, which is sometimes useful
|
||||
* Progress Condition: wait-free population oblivious
|
||||
*/
|
||||
T* protectRelease(int index, T* ptr, const int tid) {
|
||||
hp[tid][index].store(ptr, std::memory_order_release);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress Condition: wait-free bounded (by the number of threads squared)
|
||||
*/
|
||||
void retire(T* ptr, const int tid) {
|
||||
retiredList[tid * CLPAD].push_back(ptr);
|
||||
if (retiredList[tid * CLPAD].size() < HP_THRESHOLD_R)
|
||||
return;
|
||||
for (unsigned iret = 0; iret < retiredList[tid * CLPAD].size();) {
|
||||
auto obj = retiredList[tid * CLPAD][iret];
|
||||
bool canDelete = true;
|
||||
for (int tid = 0; tid < maxThreads && canDelete; tid++) {
|
||||
for (int ihp = maxHPs - 1; ihp >= 0; ihp--) {
|
||||
if (hp[tid][ihp].load() == obj) {
|
||||
canDelete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canDelete) {
|
||||
retiredList[tid * CLPAD].erase(retiredList[tid * CLPAD].begin() + iret);
|
||||
delete obj;
|
||||
continue;
|
||||
}
|
||||
iret++;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace ConcurrencyFreaks
|
||||
|
||||
#endif /* _HAZARD_POINTERS_H_ */
|
313
tdactor/benchmark/third_party/LCRQueue.h
vendored
Normal file
313
tdactor/benchmark/third_party/LCRQueue.h
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
/******************************************************************************
|
||||
* Copyright (c) 2014-2016, Pedro Ramalhete, Andreia Correia
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Concurrency Freaks nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef _LCRQ_QUEUE_HP_H_
|
||||
#define _LCRQ_QUEUE_HP_H_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// CAS2 macro
|
||||
|
||||
#define __CAS2(ptr, o1, o2, n1, n2) \
|
||||
({ \
|
||||
char __ret; \
|
||||
__typeof__(o2) __junk; \
|
||||
__typeof__(*(ptr)) __old1 = (o1); \
|
||||
__typeof__(o2) __old2 = (o2); \
|
||||
__typeof__(*(ptr)) __new1 = (n1); \
|
||||
__typeof__(o2) __new2 = (n2); \
|
||||
asm volatile("lock cmpxchg16b %2;setz %1" \
|
||||
: "=d"(__junk), "=a"(__ret), "+m"(*ptr) \
|
||||
: "b"(__new1), "c"(__new2), "a"(__old1), "d"(__old2)); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
#define CAS2(ptr, o1, o2, n1, n2) __CAS2(ptr, o1, o2, n1, n2)
|
||||
|
||||
#define BIT_TEST_AND_SET(ptr, b) \
|
||||
({ \
|
||||
char __ret; \
|
||||
asm volatile("lock btsq $63, %0; setnc %1" : "+m"(*ptr), "=a"(__ret) : : "cc"); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
/**
|
||||
* <h1> LCRQ Queue </h1>
|
||||
*
|
||||
* This is LCRQ by Adam Morrison and Yehuda Afek
|
||||
* http://www.cs.tau.ac.il/~mad/publications/ppopp2013-x86queues.pdf
|
||||
*
|
||||
* This implementation does NOT obey the C++ memory model rules AND it is x86 specific.
|
||||
* No guarantees are given on the correctness or consistency of the results if you use this queue.
|
||||
*
|
||||
* Bugs fixed:
|
||||
* tt was not initialized in dequeue();
|
||||
*
|
||||
* <p>
|
||||
* enqueue algorithm: MS enqueue + LCRQ with re-usage
|
||||
* dequeue algorithm: MS dequeue + LCRQ with re-usage
|
||||
* Consistency: Linearizable
|
||||
* enqueue() progress: lock-free
|
||||
* dequeue() progress: lock-free
|
||||
* Memory Reclamation: Hazard Pointers (lock-free)
|
||||
*
|
||||
* <p>
|
||||
* The paper on Hazard Pointers is named "Hazard Pointers: Safe Memory
|
||||
* Reclamation for Lock-Free objects" and it is available here:
|
||||
* http://web.cecs.pdx.edu/~walpole/class/cs510/papers/11.pdf
|
||||
*
|
||||
* @author Pedro Ramalhete
|
||||
* @author Andreia Correia
|
||||
*/
|
||||
namespace ConcurrencyFreaks {
|
||||
template <typename T>
|
||||
class LCRQueue {
|
||||
private:
|
||||
static const int RING_POW = 10;
|
||||
static const uint64_t RING_SIZE = 1ull << RING_POW;
|
||||
|
||||
struct Cell {
|
||||
std::atomic<T*> val;
|
||||
std::atomic<uint64_t> idx;
|
||||
uint64_t pad[14];
|
||||
} __attribute__((aligned(128)));
|
||||
|
||||
struct Node {
|
||||
std::atomic<int64_t> head __attribute__((aligned(128)));
|
||||
std::atomic<int64_t> tail __attribute__((aligned(128)));
|
||||
std::atomic<Node*> next __attribute__((aligned(128)));
|
||||
Cell array[RING_SIZE];
|
||||
|
||||
Node() {
|
||||
for (unsigned i = 0; i < RING_SIZE; i++) {
|
||||
array[i].val.store(nullptr, std::memory_order_relaxed);
|
||||
array[i].idx.store(i, std::memory_order_relaxed);
|
||||
}
|
||||
head.store(0, std::memory_order_relaxed);
|
||||
tail.store(0, std::memory_order_relaxed);
|
||||
next.store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
alignas(128) std::atomic<Node*> head;
|
||||
alignas(128) std::atomic<Node*> tail;
|
||||
|
||||
static const int MAX_THREADS = 128;
|
||||
const int maxThreads;
|
||||
|
||||
HazardPointers<Node> hp{1, maxThreads};
|
||||
const int kHpTail = 0;
|
||||
const int kHpHead = 0;
|
||||
|
||||
/*
|
||||
* Private methods
|
||||
*/
|
||||
int is_empty(T* v) {
|
||||
return (v == nullptr);
|
||||
}
|
||||
|
||||
uint64_t node_index(uint64_t i) {
|
||||
return (i & ~(1ull << 63));
|
||||
}
|
||||
|
||||
uint64_t set_unsafe(uint64_t i) {
|
||||
return (i | (1ull << 63));
|
||||
}
|
||||
|
||||
uint64_t node_unsafe(uint64_t i) {
|
||||
return (i & (1ull << 63));
|
||||
}
|
||||
|
||||
inline uint64_t tail_index(uint64_t t) {
|
||||
return (t & ~(1ull << 63));
|
||||
}
|
||||
|
||||
int crq_is_closed(uint64_t t) {
|
||||
return (t & (1ull << 63)) != 0;
|
||||
}
|
||||
|
||||
void fixState(Node* lhead) {
|
||||
while (1) {
|
||||
uint64_t t = lhead->tail.fetch_add(0);
|
||||
uint64_t h = lhead->head.fetch_add(0);
|
||||
// TODO: is it ok or not to cast "t" to int64_t ?
|
||||
if (lhead->tail.load() != (int64_t)t)
|
||||
continue;
|
||||
if (h > t) {
|
||||
int64_t tmp = t;
|
||||
if (lhead->tail.compare_exchange_strong(tmp, h))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int close_crq(Node* rq, const uint64_t tailticket, const int tries) {
|
||||
if (tries < 10) {
|
||||
int64_t tmp = tailticket + 1;
|
||||
return rq->tail.compare_exchange_strong(tmp, (tailticket + 1) | (1ull << 63));
|
||||
} else {
|
||||
return BIT_TEST_AND_SET(&rq->tail, 63);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
LCRQueue(int maxThreads = MAX_THREADS) : maxThreads{maxThreads} {
|
||||
// Shared object init
|
||||
Node* sentinel = new Node;
|
||||
head.store(sentinel, std::memory_order_relaxed);
|
||||
tail.store(sentinel, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
~LCRQueue() {
|
||||
while (dequeue(0) != nullptr)
|
||||
; // Drain the queue
|
||||
delete head.load(); // Delete the last node
|
||||
}
|
||||
|
||||
std::string className() {
|
||||
return "LCRQueue";
|
||||
}
|
||||
|
||||
void enqueue(T* item, const int tid) {
|
||||
int try_close = 0;
|
||||
while (true) {
|
||||
Node* ltail = hp.protectPtr(kHpTail, tail.load(), tid);
|
||||
if (ltail != tail.load())
|
||||
continue;
|
||||
Node* lnext = ltail->next.load();
|
||||
if (lnext != nullptr) { // Help advance the tail
|
||||
tail.compare_exchange_strong(ltail, lnext);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t tailticket = ltail->tail.fetch_add(1);
|
||||
if (crq_is_closed(tailticket)) {
|
||||
Node* newNode = new Node();
|
||||
// Solo enqueue (superfluous?)
|
||||
newNode->tail.store(1, std::memory_order_relaxed);
|
||||
newNode->array[0].val.store(item, std::memory_order_relaxed);
|
||||
newNode->array[0].idx.store(0, std::memory_order_relaxed);
|
||||
Node* nullnode = nullptr;
|
||||
if (ltail->next.compare_exchange_strong(nullnode, newNode)) { // Insert new ring
|
||||
tail.compare_exchange_strong(ltail, newNode); // Advance the tail
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
delete newNode;
|
||||
continue;
|
||||
}
|
||||
Cell* cell = <ail->array[tailticket & (RING_SIZE - 1)];
|
||||
uint64_t idx = cell->idx.load();
|
||||
if (cell->val.load() == nullptr) {
|
||||
if (node_index(idx) <= tailticket) {
|
||||
// TODO: is the missing cast before "t" ok or not to add?
|
||||
if ((!node_unsafe(idx) || ltail->head.load() < (int64_t)tailticket)) {
|
||||
if (CAS2((void**)cell, nullptr, idx, item, tailticket)) {
|
||||
hp.clear(tid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (((int64_t)(tailticket - ltail->head.load()) >= (int64_t)RING_SIZE) &&
|
||||
close_crq(ltail, tailticket, ++try_close))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
T* dequeue(const int tid) {
|
||||
while (true) {
|
||||
Node* lhead = hp.protectPtr(kHpHead, head.load(), tid);
|
||||
if (lhead != head.load())
|
||||
continue;
|
||||
uint64_t headticket = lhead->head.fetch_add(1);
|
||||
Cell* cell = &lhead->array[headticket & (RING_SIZE - 1)];
|
||||
|
||||
int r = 0;
|
||||
uint64_t tt = 0;
|
||||
|
||||
while (true) {
|
||||
uint64_t cell_idx = cell->idx.load();
|
||||
uint64_t unsafe = node_unsafe(cell_idx);
|
||||
uint64_t idx = node_index(cell_idx);
|
||||
T* val = cell->val.load();
|
||||
|
||||
if (idx > headticket)
|
||||
break;
|
||||
|
||||
if (val != nullptr) {
|
||||
if (idx == headticket) {
|
||||
if (CAS2((void**)cell, val, cell_idx, nullptr, unsafe | (headticket + RING_SIZE))) {
|
||||
hp.clear(tid);
|
||||
return val;
|
||||
}
|
||||
} else {
|
||||
if (CAS2((void**)cell, val, cell_idx, val, set_unsafe(idx)))
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if ((r & ((1ull << 10) - 1)) == 0)
|
||||
tt = lhead->tail.load();
|
||||
// Optimization: try to bail quickly if queue is closed.
|
||||
int crq_closed = crq_is_closed(tt);
|
||||
uint64_t t = tail_index(tt);
|
||||
if (unsafe) { // Nothing to do, move along
|
||||
if (CAS2((void**)cell, val, cell_idx, val, unsafe | (headticket + RING_SIZE)))
|
||||
break;
|
||||
} else if (t < headticket + 1 || r > 200000 || crq_closed) {
|
||||
if (CAS2((void**)cell, val, idx, val, headticket + RING_SIZE)) {
|
||||
if (r > 200000 && tt > RING_SIZE)
|
||||
BIT_TEST_AND_SET(&lhead->tail, 63);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
++r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tail_index(lhead->tail.load()) <= headticket + 1) {
|
||||
fixState(lhead);
|
||||
// try to return empty
|
||||
Node* lnext = lhead->next.load();
|
||||
if (lnext == nullptr) {
|
||||
hp.clear(tid);
|
||||
return nullptr; // Queue is empty
|
||||
}
|
||||
if (tail_index(lhead->tail) <= headticket + 1) {
|
||||
if (head.compare_exchange_strong(lhead, lnext))
|
||||
hp.retire(lhead, tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace ConcurrencyFreaks
|
||||
#endif /* _LCRQ_QUEUE_HP_H_ */
|
0
tdactor/benchmark/third_party/LazyIndexArrayQueue.h
vendored
Normal file
0
tdactor/benchmark/third_party/LazyIndexArrayQueue.h
vendored
Normal file
3716
tdactor/benchmark/third_party/MoodyCamelQueue.h
vendored
Normal file
3716
tdactor/benchmark/third_party/MoodyCamelQueue.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
684
tdactor/benchmark/third_party/mp-queue.c
vendored
Normal file
684
tdactor/benchmark/third_party/mp-queue.c
vendored
Normal file
|
@ -0,0 +1,684 @@
|
|||
/*
|
||||
This file is part of KittenDB-Engine Library.
|
||||
|
||||
KittenDB-Engine Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KittenDB-Engine Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with KittenDB-Engine Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2014-2016 Telegraph Inc
|
||||
2014-2016 Nikolai Durov
|
||||
2014 Andrey Lopatin
|
||||
*/
|
||||
|
||||
char disable_linker_warning_about_empty_file_mp_queue_cpp;
|
||||
|
||||
#ifdef TG_LCR_QUEUE
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/futex.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "mp-queue.h"
|
||||
|
||||
#undef assert
|
||||
#ifndef assert
|
||||
#define assert(x) x
|
||||
#endif
|
||||
|
||||
volatile int mpq_blocks_allocated, mpq_blocks_allocated_max, mpq_blocks_allocations, mpq_blocks_true_allocations,
|
||||
mpq_blocks_wasted, mpq_blocks_prepared;
|
||||
volatile int mpq_small_blocks_allocated, mpq_small_blocks_allocated_max;
|
||||
|
||||
__thread int mpq_this_thread_id;
|
||||
__thread void **thread_hazard_pointers;
|
||||
volatile int mpq_threads;
|
||||
|
||||
struct mp_queue MqGarbageBlocks, MqPreparedBlocks;
|
||||
struct mp_queue MqGarbageSmallBlocks, MqPreparedSmallBlocks;
|
||||
|
||||
static inline void barrier(void) {
|
||||
asm volatile("" : : : "memory");
|
||||
}
|
||||
static inline void mfence(void) {
|
||||
asm volatile("mfence" : : : "memory");
|
||||
}
|
||||
|
||||
/* hazard pointers, one per thread */
|
||||
|
||||
void *mqb_hazard_ptr[MAX_MPQ_THREADS][THREAD_HPTRS] __attribute__((aligned(64)));
|
||||
|
||||
int is_hazard_ptr(void *ptr, int a, int b) {
|
||||
barrier();
|
||||
int k = mpq_threads, q = mpq_this_thread_id;
|
||||
barrier();
|
||||
int i, j, r = 0;
|
||||
for (j = a; j <= b; j++) {
|
||||
if (mqb_hazard_ptr[q][j] == ptr) {
|
||||
r = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 1; i <= k; i++) {
|
||||
if (i == q) {
|
||||
continue;
|
||||
}
|
||||
for (j = a; j <= b; j++) {
|
||||
if (mqb_hazard_ptr[i][j] == ptr) {
|
||||
barrier();
|
||||
return r + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
return r;
|
||||
}
|
||||
|
||||
void clear_thread_ids() {
|
||||
mpq_threads = 0;
|
||||
mpq_this_thread_id = 0;
|
||||
}
|
||||
/* initialize this thread id and return it */
|
||||
int get_this_thread_id(void) {
|
||||
int i = mpq_this_thread_id;
|
||||
if (i) {
|
||||
return i;
|
||||
}
|
||||
i = __sync_fetch_and_add(&mpq_threads, 1) + 1;
|
||||
assert(i > 0 && i < MAX_MPQ_THREADS);
|
||||
thread_hazard_pointers = mqb_hazard_ptr[i];
|
||||
return mpq_this_thread_id = i;
|
||||
}
|
||||
|
||||
/* custom semaphore implementation using futexes */
|
||||
|
||||
int mp_sem_post(mp_sem_t *sem) {
|
||||
__sync_fetch_and_add(&sem->value, 1);
|
||||
if (sem->waiting > 0) {
|
||||
syscall(__NR_futex, &sem->value, FUTEX_WAKE, 1, NULL, 0, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_sem_wait(mp_sem_t *sem) {
|
||||
int v = sem->value, q = 0;
|
||||
while (1) {
|
||||
if (v > 0) {
|
||||
v = __sync_fetch_and_add(&sem->value, -1);
|
||||
if (v > 0) {
|
||||
return 0;
|
||||
}
|
||||
v = __sync_add_and_fetch(&sem->value, 1);
|
||||
} else {
|
||||
if (v < 0 && q++ < 10) {
|
||||
barrier();
|
||||
v = sem->value;
|
||||
continue;
|
||||
}
|
||||
__sync_fetch_and_add(&sem->waiting, 1);
|
||||
syscall(__NR_futex, &sem->value, FUTEX_WAIT, v, NULL, 0, 0);
|
||||
__sync_fetch_and_add(&sem->waiting, -1);
|
||||
v = sem->value;
|
||||
q = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mp_sem_trywait(mp_sem_t *sem) {
|
||||
int v = sem->value;
|
||||
if (v > 0) {
|
||||
v = __sync_fetch_and_add(&sem->value, -1);
|
||||
if (v > 0) {
|
||||
return 0;
|
||||
}
|
||||
__sync_fetch_and_add(&sem->value, 1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* functions for one mp_queue_block */
|
||||
|
||||
// may invoke mpq_pop()/mpq_push() if allow_recursion=1
|
||||
struct mp_queue_block *alloc_mpq_block(mqn_value_t first_val, int allow_recursion, int is_small) {
|
||||
is_small = 0;
|
||||
struct mp_queue_block *QB = 0;
|
||||
int prepared = 0, align_bytes = 0;
|
||||
long size = (is_small ? MPQ_SMALL_BLOCK_SIZE : MPQ_BLOCK_SIZE);
|
||||
if (allow_recursion) {
|
||||
QB = mpq_pop(is_small ? &MqGarbageSmallBlocks : &MqGarbageBlocks, MPQF_RECURSIVE);
|
||||
if (QB) {
|
||||
if (!is_hazard_ptr(QB, 0, 2)) {
|
||||
// reclaiming garbage
|
||||
assert(QB->mqb_magic == MQ_BLOCK_GARBAGE_MAGIC);
|
||||
__sync_fetch_and_add(&mpq_blocks_wasted, -1);
|
||||
align_bytes = QB->mqb_align_bytes;
|
||||
} else {
|
||||
mpq_push(is_small ? &MqGarbageSmallBlocks : &MqGarbageBlocks, QB, MPQF_RECURSIVE);
|
||||
QB = 0;
|
||||
}
|
||||
}
|
||||
if (!QB) {
|
||||
QB = mpq_pop(is_small ? &MqPreparedSmallBlocks : &MqPreparedBlocks, MPQF_RECURSIVE);
|
||||
if (QB) {
|
||||
assert(QB->mqb_magic == MQ_BLOCK_PREPARED_MAGIC);
|
||||
prepared = 1;
|
||||
__sync_fetch_and_add(&mpq_blocks_prepared, -1);
|
||||
align_bytes = QB->mqb_align_bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!QB) {
|
||||
char *new_block = malloc(offsetof(struct mp_queue_block, mqb_nodes) + size * (2 * sizeof(void *)) +
|
||||
MPQ_BLOCK_ALIGNMENT - sizeof(void *));
|
||||
assert(new_block);
|
||||
assert(!((long)new_block & (sizeof(void *) - 1)));
|
||||
align_bytes = -(int)(long)new_block & (MPQ_BLOCK_ALIGNMENT - 1);
|
||||
QB = (struct mp_queue_block *)(new_block + align_bytes);
|
||||
|
||||
__sync_fetch_and_add(&mpq_blocks_true_allocations, 1);
|
||||
if (is_small) {
|
||||
int t = __sync_fetch_and_add(&mpq_small_blocks_allocated, 1);
|
||||
if (t >= mpq_small_blocks_allocated_max) {
|
||||
__sync_bool_compare_and_swap(&mpq_small_blocks_allocated_max, mpq_small_blocks_allocated_max, t + 1);
|
||||
}
|
||||
} else {
|
||||
int t = __sync_fetch_and_add(&mpq_blocks_allocated, 1);
|
||||
if (t >= mpq_blocks_allocated_max) {
|
||||
__sync_bool_compare_and_swap(&mpq_blocks_allocated_max, mpq_blocks_allocated_max, t + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(QB->mqb_size == size);
|
||||
}
|
||||
__sync_fetch_and_add(&mpq_blocks_allocations, 1);
|
||||
|
||||
memset(QB, 0, offsetof(struct mp_queue_block, mqb_nodes));
|
||||
QB->mqb_align_bytes = align_bytes;
|
||||
QB->mqb_size = size;
|
||||
|
||||
QB->mqb_nodes[0].idx = MQN_SAFE;
|
||||
QB->mqb_nodes[0].val = first_val;
|
||||
|
||||
if (!prepared) {
|
||||
long i;
|
||||
for (i = 1; i < size; i++) {
|
||||
QB->mqb_nodes[i].idx = MQN_SAFE + i;
|
||||
QB->mqb_nodes[i].val = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (first_val) {
|
||||
QB->mqb_tail = 1;
|
||||
}
|
||||
|
||||
QB->mqb_magic = MQ_BLOCK_USED_MAGIC;
|
||||
return QB;
|
||||
}
|
||||
|
||||
void free_mpq_block(struct mp_queue_block *QB) {
|
||||
assert(QB->mqb_magic == MQ_BLOCK_USED_MAGIC);
|
||||
assert((unsigned)QB->mqb_align_bytes < MPQ_BLOCK_ALIGNMENT && !(QB->mqb_align_bytes & (sizeof(void *) - 1)));
|
||||
QB->mqb_magic = MQ_BLOCK_FREE_MAGIC;
|
||||
if (QB->mqb_size == MPQ_SMALL_BLOCK_SIZE) {
|
||||
__sync_fetch_and_add(&mpq_small_blocks_allocated, -1);
|
||||
} else {
|
||||
assert(QB->mqb_size == MPQ_BLOCK_SIZE);
|
||||
__sync_fetch_and_add(&mpq_blocks_allocated, -1);
|
||||
}
|
||||
free((char *)QB - QB->mqb_align_bytes);
|
||||
}
|
||||
|
||||
static inline void mpq_fix_state(struct mp_queue_block *QB) {
|
||||
long h, t;
|
||||
while (1) {
|
||||
barrier();
|
||||
t = QB->mqb_tail;
|
||||
barrier();
|
||||
h = QB->mqb_head;
|
||||
barrier();
|
||||
if ((unsigned long)h <= (unsigned long)t) {
|
||||
break;
|
||||
}
|
||||
if (QB->mqb_tail != t) {
|
||||
continue;
|
||||
}
|
||||
// here tail < head ; try to advance tail to head
|
||||
// (or to some value h such that tail < h <= head)
|
||||
if (__sync_bool_compare_and_swap(&QB->mqb_tail, t, h)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mqn_value_t mpq_block_pop(struct mp_queue_block *QB) {
|
||||
// fprintf (stderr, "%d:mpq_block_pop(%p)\n", mpq_this_thread_id, QB);
|
||||
long size = QB->mqb_size;
|
||||
while (1) {
|
||||
long h = __sync_fetch_and_add(&QB->mqb_head, 1);
|
||||
// fprintf (stderr, "%d: mpq_block_pop(%ld)\n", mpq_this_thread_id, h);
|
||||
mpq_node_t *node = &QB->mqb_nodes[h & (size - 1)];
|
||||
while (1) {
|
||||
mpq_node_t d, e;
|
||||
barrier();
|
||||
mqn_value_t val = node->val;
|
||||
barrier();
|
||||
long safe_idx = node->idx;
|
||||
barrier();
|
||||
long idx = safe_idx & MQN_IDX_MASK;
|
||||
if (idx > h) {
|
||||
break;
|
||||
}
|
||||
d.val = val;
|
||||
d.idx = safe_idx;
|
||||
if (val) {
|
||||
if (idx == h) {
|
||||
e.idx = safe_idx + size;
|
||||
e.val = 0;
|
||||
if (__sync_bool_compare_and_swap(&node->pair, d.pair, e.pair)) {
|
||||
// fprintf (stderr, "%d: mpq_block_pop(%ld) -> %lx\n", mpq_this_thread_id, h, (long) val);
|
||||
return val;
|
||||
}
|
||||
} else {
|
||||
e.val = val;
|
||||
e.idx = idx; // clear 'safe' flag
|
||||
if (__sync_bool_compare_and_swap(&node->pair, d.pair, e.pair)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e.idx = (safe_idx & MQN_SAFE) + h + size;
|
||||
e.val = 0;
|
||||
if (__sync_bool_compare_and_swap(&node->pair, d.pair, e.pair)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* somebody changed this element while we were inspecting it, make another loop iteration */
|
||||
}
|
||||
barrier();
|
||||
long t = QB->mqb_tail & MQN_IDX_MASK;
|
||||
barrier();
|
||||
if (t <= h + 1) {
|
||||
mpq_fix_state(QB);
|
||||
return 0;
|
||||
}
|
||||
/* now try again with a new value of h */
|
||||
}
|
||||
}
|
||||
|
||||
long mpq_block_push(struct mp_queue_block *QB, mqn_value_t val) {
|
||||
int iterations = 0;
|
||||
long size = QB->mqb_size;
|
||||
// fprintf (stderr, "%d:mpq_block_push(%p)\n", mpq_this_thread_id, QB);
|
||||
while (1) {
|
||||
long t = __sync_fetch_and_add(&QB->mqb_tail, 1);
|
||||
// fprintf (stderr, "%d: mpq_block_push(%ld)\n", mpq_this_thread_id, t);
|
||||
if (t & MQN_SAFE) {
|
||||
return -1L; // bad luck
|
||||
}
|
||||
mpq_node_t *node = &QB->mqb_nodes[t & (size - 1)];
|
||||
barrier();
|
||||
mqn_value_t old_val = node->val;
|
||||
barrier();
|
||||
long safe_idx = node->idx;
|
||||
barrier();
|
||||
long idx = safe_idx & MQN_IDX_MASK;
|
||||
if (!old_val && idx <= t && ((safe_idx & MQN_SAFE) || QB->mqb_head <= t)) {
|
||||
mpq_node_t d, e;
|
||||
d.idx = safe_idx;
|
||||
d.val = 0;
|
||||
e.idx = MQN_SAFE + t;
|
||||
e.val = val;
|
||||
if (__sync_bool_compare_and_swap(&node->pair, d.pair, e.pair)) {
|
||||
// fprintf (stderr, "%d: mpq_block_push(%ld) <- %lx\n", mpq_this_thread_id, t, (long) val);
|
||||
return t; // pushed OK
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
long h = QB->mqb_head;
|
||||
barrier();
|
||||
if (t - h >= size || ++iterations > 10) {
|
||||
__sync_fetch_and_or(&QB->mqb_tail, MQN_SAFE); // closing queue
|
||||
return -1L; // bad luck
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* functions for mp_queue = list of mp_queue_block's */
|
||||
void init_mp_queue(struct mp_queue *MQ) {
|
||||
assert(MQ->mq_magic != MQ_MAGIC && MQ->mq_magic != MQ_MAGIC_SEM);
|
||||
memset(MQ, 0, sizeof(struct mp_queue));
|
||||
MQ->mq_head = MQ->mq_tail = alloc_mpq_block(0, 0, 1);
|
||||
MQ->mq_magic = MQ_MAGIC;
|
||||
|
||||
if (!MqGarbageBlocks.mq_magic) {
|
||||
init_mp_queue(&MqGarbageBlocks);
|
||||
init_mp_queue(&MqGarbageSmallBlocks);
|
||||
} else if (!MqPreparedBlocks.mq_magic) {
|
||||
init_mp_queue(&MqPreparedBlocks);
|
||||
init_mp_queue(&MqPreparedSmallBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
void init_mp_queue_w(struct mp_queue *MQ) {
|
||||
init_mp_queue(MQ);
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
sem_init(&MQ->mq_sem, 0, 0);
|
||||
#endif
|
||||
MQ->mq_magic = MQ_MAGIC_SEM;
|
||||
}
|
||||
|
||||
struct mp_queue *alloc_mp_queue(void) {
|
||||
struct mp_queue *MQ = NULL;
|
||||
assert(!posix_memalign((void **)&MQ, 64, sizeof(*MQ)));
|
||||
memset(MQ, 0, sizeof(*MQ));
|
||||
init_mp_queue(MQ);
|
||||
return MQ;
|
||||
}
|
||||
|
||||
struct mp_queue *alloc_mp_queue_w(void) {
|
||||
struct mp_queue *MQ = NULL;
|
||||
assert(!posix_memalign((void **)&MQ, 64, sizeof(*MQ)));
|
||||
memset(MQ, 0, sizeof(*MQ));
|
||||
init_mp_queue_w(MQ);
|
||||
return MQ;
|
||||
}
|
||||
|
||||
/* invoke only if sure that nobody else may be using this mp_queue in parallel */
|
||||
void clear_mp_queue(struct mp_queue *MQ) {
|
||||
assert(MQ->mq_magic == MQ_MAGIC || MQ->mq_magic == MQ_MAGIC_SEM);
|
||||
assert(MQ->mq_head && MQ->mq_tail);
|
||||
struct mp_queue_block *QB = MQ->mq_head, *QBN;
|
||||
for (QB = MQ->mq_head; QB; QB = QBN) {
|
||||
QBN = QB->mqb_next;
|
||||
assert(QB->mqb_next || QB == MQ->mq_tail);
|
||||
QB->mqb_next = 0;
|
||||
free_mpq_block(QB);
|
||||
}
|
||||
MQ->mq_head = MQ->mq_tail = 0;
|
||||
MQ->mq_magic = 0;
|
||||
}
|
||||
|
||||
void free_mp_queue(struct mp_queue *MQ) {
|
||||
clear_mp_queue(MQ);
|
||||
free(MQ);
|
||||
}
|
||||
|
||||
// may invoke mpq_push() to discard new empty block
|
||||
mqn_value_t mpq_pop(struct mp_queue *MQ, int flags) {
|
||||
void **hptr = &mqb_hazard_ptr[get_this_thread_id()][0];
|
||||
long r = ((flags & MPQF_RECURSIVE) != 0);
|
||||
struct mp_queue_block *QB;
|
||||
mqn_value_t v;
|
||||
while (1) {
|
||||
QB = MQ->mq_head;
|
||||
barrier();
|
||||
hptr[r] = QB;
|
||||
barrier();
|
||||
__sync_synchronize();
|
||||
if (MQ->mq_head != QB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
v = mpq_block_pop(QB);
|
||||
if (v) {
|
||||
break;
|
||||
}
|
||||
barrier();
|
||||
if (!QB->mqb_next) {
|
||||
QB = 0;
|
||||
break;
|
||||
}
|
||||
v = mpq_block_pop(QB);
|
||||
if (v) {
|
||||
break;
|
||||
}
|
||||
if (__sync_bool_compare_and_swap(&MQ->mq_head, QB, QB->mqb_next)) {
|
||||
// want to free QB here, but this is complicated if somebody else holds a pointer
|
||||
if (is_hazard_ptr(QB, 0, 2) <= 1) {
|
||||
free_mpq_block(QB);
|
||||
} else {
|
||||
__sync_fetch_and_add(&mpq_blocks_wasted, 1);
|
||||
// ... put QB into some GC queue? ...
|
||||
QB->mqb_magic = MQ_BLOCK_GARBAGE_MAGIC;
|
||||
mpq_push(QB->mqb_size == MPQ_SMALL_BLOCK_SIZE ? &MqGarbageSmallBlocks : &MqGarbageBlocks, QB,
|
||||
flags & MPQF_RECURSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags & MPQF_STORE_PTR) {
|
||||
hptr[2] = QB;
|
||||
}
|
||||
hptr[r] = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* 1 = definitely empty (for some serialization), 0 = possibly non-empty;
|
||||
may invoke mpq_push() to discard empty block */
|
||||
int mpq_is_empty(struct mp_queue *MQ) {
|
||||
void **hptr = &mqb_hazard_ptr[get_this_thread_id()][0];
|
||||
struct mp_queue_block *QB;
|
||||
while (1) {
|
||||
QB = MQ->mq_head;
|
||||
barrier();
|
||||
*hptr = QB;
|
||||
barrier();
|
||||
__sync_synchronize();
|
||||
if (MQ->mq_head != QB) {
|
||||
continue;
|
||||
}
|
||||
barrier();
|
||||
long h = QB->mqb_head;
|
||||
barrier();
|
||||
long t = QB->mqb_tail;
|
||||
barrier();
|
||||
if (!(t & MQN_SAFE)) {
|
||||
*hptr = 0;
|
||||
return t <= h;
|
||||
}
|
||||
t &= MQN_IDX_MASK;
|
||||
if (t > h) {
|
||||
*hptr = 0;
|
||||
return 0;
|
||||
}
|
||||
barrier();
|
||||
if (!QB->mqb_next) {
|
||||
*hptr = 0;
|
||||
return 1;
|
||||
}
|
||||
if (__sync_bool_compare_and_swap(&MQ->mq_head, QB, QB->mqb_next)) {
|
||||
// want to free QB here, but this is complicated if somebody else holds a pointer
|
||||
if (is_hazard_ptr(QB, 0, 2) <= 1) {
|
||||
free_mpq_block(QB);
|
||||
} else {
|
||||
__sync_fetch_and_add(&mpq_blocks_wasted, 1);
|
||||
// ... put QB into some GC queue? ...
|
||||
QB->mqb_magic = MQ_BLOCK_GARBAGE_MAGIC;
|
||||
mpq_push(QB->mqb_size == MPQ_SMALL_BLOCK_SIZE ? &MqGarbageSmallBlocks : &MqGarbageBlocks, QB, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
*hptr = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* may invoke mpq_alloc_block (which recursively invokes mpq_pop)
|
||||
or mpq_push() (without needing to hold hazard pointer) to deal with blocks */
|
||||
long mpq_push(struct mp_queue *MQ, mqn_value_t val, int flags) {
|
||||
void **hptr = mqb_hazard_ptr[get_this_thread_id()];
|
||||
long r = ((flags & MPQF_RECURSIVE) != 0);
|
||||
while (1) {
|
||||
struct mp_queue_block *QB = MQ->mq_tail;
|
||||
barrier();
|
||||
hptr[r] = QB;
|
||||
barrier();
|
||||
__sync_synchronize();
|
||||
if (MQ->mq_tail != QB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (QB->mqb_next) {
|
||||
__sync_bool_compare_and_swap(&MQ->mq_tail, QB, QB->mqb_next);
|
||||
continue;
|
||||
}
|
||||
long pos = mpq_block_push(QB, val);
|
||||
if (pos >= 0) {
|
||||
if (flags & MPQF_STORE_PTR) {
|
||||
hptr[2] = QB;
|
||||
}
|
||||
hptr[r] = 0;
|
||||
return pos;
|
||||
}
|
||||
#define DBG(c) // fprintf (stderr, "[%d] pushing %lx to %p,%p: %c\n", mpq_this_thread_id, (long) val, MQ, QB, c);
|
||||
DBG('A');
|
||||
/*
|
||||
if (__sync_fetch_and_add (&QB->mqb_next_allocators, 1)) {
|
||||
// somebody else will allocate next block; busy wait instead of spuruous alloc/free
|
||||
DBG('B')
|
||||
while (!QB->mqb_next) {
|
||||
barrier ();
|
||||
}
|
||||
DBG('C')
|
||||
continue;
|
||||
}
|
||||
*/
|
||||
int is_small = (QB == MQ->mq_head);
|
||||
struct mp_queue_block *NQB;
|
||||
if (!r) {
|
||||
assert(!hptr[1]);
|
||||
NQB = alloc_mpq_block(val, 1, is_small);
|
||||
assert(!hptr[1]);
|
||||
} else {
|
||||
NQB = alloc_mpq_block(val, 0, is_small);
|
||||
}
|
||||
assert(hptr[r] == QB);
|
||||
DBG('D')
|
||||
if (__sync_bool_compare_and_swap(&QB->mqb_next, 0, NQB)) {
|
||||
__sync_bool_compare_and_swap(&MQ->mq_tail, QB, NQB);
|
||||
DBG('E')
|
||||
if (flags & MPQF_STORE_PTR) {
|
||||
hptr[2] = NQB;
|
||||
}
|
||||
hptr[r] = 0;
|
||||
return 0;
|
||||
} else {
|
||||
DBG('F');
|
||||
NQB->mqb_magic = MQ_BLOCK_PREPARED_MAGIC;
|
||||
mpq_push(is_small ? &MqPreparedSmallBlocks : &MqPreparedBlocks, NQB, 0);
|
||||
__sync_fetch_and_add(&mpq_blocks_prepared, 1);
|
||||
}
|
||||
}
|
||||
#undef DBG
|
||||
}
|
||||
|
||||
mqn_value_t mpq_pop_w(struct mp_queue *MQ, int flags) {
|
||||
assert(MQ->mq_magic == MQ_MAGIC_SEM);
|
||||
int s = -1, iterations = flags & MPQF_MAX_ITERATIONS;
|
||||
while (iterations-- > 0) {
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
s = sem_trywait(&MQ->mq_sem);
|
||||
#else
|
||||
s = mp_sem_trywait(&MQ->mq_sem);
|
||||
#endif
|
||||
if (!s) {
|
||||
break;
|
||||
}
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
assert(errno == EAGAIN || errno == EINTR);
|
||||
#endif
|
||||
}
|
||||
while (s < 0) {
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
s = sem_wait(&MQ->mq_sem);
|
||||
#else
|
||||
s = mp_sem_wait(&MQ->mq_sem);
|
||||
#endif
|
||||
if (!s) {
|
||||
break;
|
||||
}
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
assert(errno == EAGAIN);
|
||||
#endif
|
||||
}
|
||||
mqn_value_t *v = mpq_pop(MQ, flags);
|
||||
assert(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
mqn_value_t mpq_pop_nw(struct mp_queue *MQ, int flags) {
|
||||
assert(MQ->mq_magic == MQ_MAGIC_SEM);
|
||||
int s = -1, iterations = flags & MPQF_MAX_ITERATIONS;
|
||||
while (iterations-- > 0) {
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
s = sem_trywait(&MQ->mq_sem);
|
||||
#else
|
||||
s = mp_sem_trywait(&MQ->mq_sem);
|
||||
#endif
|
||||
if (s >= 0) {
|
||||
break;
|
||||
}
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
assert(errno == EAGAIN || errno == EINTR);
|
||||
#endif
|
||||
}
|
||||
if (s < 0) {
|
||||
return 0;
|
||||
}
|
||||
mqn_value_t *v = mpq_pop(MQ, flags);
|
||||
assert(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
long mpq_push_w(struct mp_queue *MQ, mqn_value_t v, int flags) {
|
||||
assert(MQ->mq_magic == MQ_MAGIC_SEM);
|
||||
long res = mpq_push(MQ, v, flags);
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
assert(sem_post(&MQ->mq_sem) >= 0);
|
||||
#else
|
||||
assert(mp_sem_post(&MQ->mq_sem) >= 0);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
void *get_ptr_multithread_copy(void **ptr, void (*incref)(void *ptr)) {
|
||||
void **hptr = &mqb_hazard_ptr[get_this_thread_id()][COMMON_HAZARD_PTR_NUM];
|
||||
assert(*hptr == NULL);
|
||||
|
||||
void *R;
|
||||
while (1) {
|
||||
R = *ptr;
|
||||
barrier();
|
||||
*hptr = R;
|
||||
barrier();
|
||||
mfence();
|
||||
|
||||
if (R != *ptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
incref(R);
|
||||
|
||||
barrier();
|
||||
*hptr = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
return R;
|
||||
}
|
||||
#endif
|
149
tdactor/benchmark/third_party/mp-queue.h
vendored
Normal file
149
tdactor/benchmark/third_party/mp-queue.h
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
This file is part of KittenDB-Engine Library.
|
||||
|
||||
KittenDB-Engine Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KittenDB-Engine Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with KittenDB-Engine Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2014 Telegraph Inc
|
||||
2014 Nikolai Durov
|
||||
2014 Andrey Lopatin
|
||||
*/
|
||||
|
||||
#ifndef __KDB_MP_QUEUE_H__
|
||||
#define __KDB_MP_QUEUE_H__
|
||||
|
||||
#define MPQ_USE_POSIX_SEMAPHORES 0
|
||||
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
typedef struct mp_semaphore {
|
||||
volatile int value;
|
||||
volatile int waiting;
|
||||
} mp_sem_t;
|
||||
|
||||
#define THREAD_HPTRS 21
|
||||
|
||||
#define MPQ_SMALL_BLOCK_SIZE 64
|
||||
#define MPQ_BLOCK_SIZE 4096 // must be a power of 2
|
||||
#define MPQ_BLOCK_ALIGNMENT 64
|
||||
|
||||
#ifdef _LP64
|
||||
typedef int int128_t __attribute__((__mode__(TI)));
|
||||
#define DLONG int128_t
|
||||
// # define DLONG __int128
|
||||
#define MQN_SAFE (-1LL << 63)
|
||||
#else
|
||||
#define DLONG long long
|
||||
#define MQN_SAFE (-1L << 31)
|
||||
#endif
|
||||
|
||||
#define MQN_IDX_MASK (~MQN_SAFE)
|
||||
|
||||
typedef void *mqn_value_t;
|
||||
|
||||
typedef struct mp_queue_node {
|
||||
union {
|
||||
struct {
|
||||
long idx;
|
||||
union {
|
||||
long mqn_value;
|
||||
void *mqn_ptr;
|
||||
mqn_value_t val;
|
||||
};
|
||||
};
|
||||
DLONG pair;
|
||||
};
|
||||
} mpq_node_t;
|
||||
|
||||
#define MQ_BLOCK_USED_MAGIC 0x1ebacaef
|
||||
#define MQ_BLOCK_FREE_MAGIC 0x2e4afeda
|
||||
#define MQ_BLOCK_GARBAGE_MAGIC 0x3a04dc7d
|
||||
#define MQ_BLOCK_PREPARED_MAGIC 0x4b9b13cd
|
||||
|
||||
#define MQ_MAGIC 0x1aed9b43
|
||||
#define MQ_MAGIC_SEM 0x1aedcd21
|
||||
|
||||
struct mp_queue_block {
|
||||
long mqb_head __attribute__((aligned(64)));
|
||||
int mqb_magic;
|
||||
int mqb_align_bytes;
|
||||
int mqb_size; // power of 2; one of MPQ_BLOCK_SIZE or MPQ_SMALL_BLOCK_SIZE
|
||||
long mqb_tail __attribute__((aligned(64)));
|
||||
struct mp_queue_block *mqb_next;
|
||||
int mqb_next_allocators;
|
||||
mpq_node_t mqb_nodes[MPQ_BLOCK_SIZE] __attribute__((aligned(64)));
|
||||
};
|
||||
|
||||
struct mp_queue {
|
||||
struct mp_queue_block *mq_head __attribute__((aligned(64)));
|
||||
int mq_magic;
|
||||
struct mp_queue_block *mq_tail __attribute__((aligned(64)));
|
||||
#if MPQ_USE_POSIX_SEMAPHORES
|
||||
sem_t mq_sem __attribute__((aligned(64)));
|
||||
#else
|
||||
mp_sem_t mq_sem __attribute__((aligned(64)));
|
||||
#endif
|
||||
};
|
||||
|
||||
extern volatile int mpq_blocks_allocated, mpq_blocks_allocated_max, mpq_blocks_allocations, mpq_blocks_true_allocations,
|
||||
mpq_blocks_wasted, mpq_blocks_prepared;
|
||||
extern volatile int mpq_small_blocks_allocated, mpq_small_blocks_allocated_max;
|
||||
|
||||
#define MAX_MPQ_THREADS 22
|
||||
extern __thread int mpq_this_thread_id;
|
||||
extern __thread void **thread_hazard_pointers;
|
||||
extern volatile int mpq_threads;
|
||||
|
||||
/* initialize this thread id and return it */
|
||||
void clear_thread_ids(void);
|
||||
int get_this_thread_id(void);
|
||||
|
||||
/* functions for one mp_queue_block */
|
||||
struct mp_queue_block *alloc_mpq_block(mqn_value_t first_val, int allow_recursion, int is_small);
|
||||
void free_mpq_block(struct mp_queue_block *QB);
|
||||
|
||||
mqn_value_t mpq_block_pop(struct mp_queue_block *QB);
|
||||
long mpq_block_push(struct mp_queue_block *QB, mqn_value_t val);
|
||||
|
||||
/* functions for mp_queue = list of mp_queue_block's */
|
||||
void init_mp_queue(struct mp_queue *MQ);
|
||||
struct mp_queue *alloc_mp_queue(void);
|
||||
struct mp_queue *alloc_mp_queue_w(void);
|
||||
void init_mp_queue_w(struct mp_queue *MQ);
|
||||
void clear_mp_queue(struct mp_queue *MQ); // frees all mpq block chain; invoke only if nobody else is using mp-queue
|
||||
void free_mp_queue(struct mp_queue *MQ); // same + invoke free()
|
||||
|
||||
// flags for mpq_push / mpq_pop functions
|
||||
#define MPQF_RECURSIVE 8192
|
||||
#define MPQF_STORE_PTR 4096
|
||||
#define MPQF_MAX_ITERATIONS (MPQF_STORE_PTR - 1)
|
||||
|
||||
long mpq_push(struct mp_queue *MQ, mqn_value_t val, int flags);
|
||||
mqn_value_t mpq_pop(struct mp_queue *MQ, int flags);
|
||||
int mpq_is_empty(struct mp_queue *MQ);
|
||||
|
||||
long mpq_push_w(struct mp_queue *MQ, mqn_value_t val, int flags);
|
||||
mqn_value_t mpq_pop_w(struct mp_queue *MQ, int flags);
|
||||
mqn_value_t mpq_pop_nw(struct mp_queue *MQ, int flags);
|
||||
|
||||
int mp_sem_post(mp_sem_t *sem);
|
||||
int mp_sem_wait(mp_sem_t *sem);
|
||||
int mp_sem_trywait(mp_sem_t *sem);
|
||||
|
||||
#define COMMON_HAZARD_PTR_NUM 3
|
||||
int is_hazard_ptr(void *ptr, int a, int b);
|
||||
extern void *mqb_hazard_ptr[MAX_MPQ_THREADS][THREAD_HPTRS];
|
||||
void *get_ptr_multithread_copy(void **ptr, void (*incref)(void *ptr));
|
||||
#endif
|
129
tdactor/td/actor/ActorId.h
Normal file
129
tdactor/td/actor/ActorId.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/common.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
template <class ActorType = core::Actor>
|
||||
class ActorId;
|
||||
template <class ActorType = core::Actor>
|
||||
class ActorOwn;
|
||||
template <class ActorType = core::Actor>
|
||||
class ActorShared;
|
||||
namespace core {
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> actor_id(SelfT *self);
|
||||
}
|
||||
|
||||
// Essentially ActorInfoWeakPtr with Type
|
||||
template <class ActorType>
|
||||
class ActorId {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
ActorId() = default;
|
||||
ActorId(const ActorId &) = default;
|
||||
ActorId &operator=(const ActorId &) = default;
|
||||
ActorId(ActorId &&other) = default;
|
||||
ActorId &operator=(ActorId &&other) = default;
|
||||
|
||||
// allow only conversion from child to parent
|
||||
template <class ToActorType, class = std::enable_if_t<std::is_base_of<ToActorType, ActorType>::value>>
|
||||
operator ActorId<ToActorType>() const {
|
||||
return ActorId<ToActorType>(ptr_);
|
||||
}
|
||||
|
||||
template <class ToActorType, class FromActorType>
|
||||
friend ActorId<ToActorType> actor_dynamic_cast(ActorId<FromActorType> from);
|
||||
|
||||
ActorType &get_actor_unsafe() const {
|
||||
return static_cast<ActorType &>(actor_info().actor());
|
||||
}
|
||||
bool empty() const {
|
||||
return !ptr_;
|
||||
}
|
||||
|
||||
bool is_alive() const {
|
||||
return !empty() && actor_info().is_alive();
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
static ActorId<ActorType> create(ActorOptions &options, ArgsT &&... args) {
|
||||
return ActorId<ActorType>(detail::create_actor<ActorType>(options, std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class OtherT>
|
||||
bool operator==(const ActorId<OtherT> &other) const {
|
||||
return ptr_ == other.ptr_;
|
||||
}
|
||||
|
||||
detail::ActorRef as_actor_ref() const {
|
||||
CHECK(!empty());
|
||||
return detail::ActorRef(*actor_info_ptr());
|
||||
}
|
||||
|
||||
const core::ActorInfoPtr &actor_info_ptr() const {
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
core::ActorInfo &actor_info() const {
|
||||
CHECK(ptr_);
|
||||
return *ptr_;
|
||||
}
|
||||
|
||||
private:
|
||||
core::ActorInfoPtr ptr_;
|
||||
|
||||
template <class OtherActorType>
|
||||
friend class ActorId;
|
||||
template <class OtherActorType>
|
||||
friend class ActorOwn;
|
||||
template <class OtherActorType>
|
||||
friend class ActorShared;
|
||||
|
||||
explicit ActorId(core::ActorInfoPtr ptr) : ptr_(std::move(ptr)) {
|
||||
}
|
||||
|
||||
template <class SelfT>
|
||||
friend ActorId<SelfT> core::actor_id(SelfT *self);
|
||||
};
|
||||
template <class ToActorType, class FromActorType>
|
||||
ActorId<ToActorType> actor_dynamic_cast(ActorId<FromActorType> from) {
|
||||
static_assert(
|
||||
std::is_base_of<FromActorType, ToActorType>::value || std::is_base_of<ToActorType, FromActorType>::value,
|
||||
"Invalid actor dynamic conversion");
|
||||
auto res = ActorId<ToActorType>(std::move(from.ptr_));
|
||||
CHECK(dynamic_cast<ToActorType *>(&res.actor_info().actor()) == &res.get_actor_unsafe());
|
||||
return res;
|
||||
}
|
||||
namespace core { // for ADL
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> actor_id(SelfT *self) {
|
||||
CHECK(self);
|
||||
CHECK(static_cast<core::Actor *>(self) == &core::ActorExecuteContext::get()->actor());
|
||||
return ActorId<SelfT>(core::ActorExecuteContext::get()->actor().get_actor_info_ptr());
|
||||
}
|
||||
|
||||
inline ActorId<> actor_id() {
|
||||
return actor_id(&core::ActorExecuteContext::get()->actor());
|
||||
}
|
||||
} // namespace core
|
||||
using core::actor_id;
|
||||
} // namespace actor
|
||||
} // namespace td
|
100
tdactor/td/actor/ActorOwn.h
Normal file
100
tdactor/td/actor/ActorOwn.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/common.h"
|
||||
#include "td/actor/ActorId.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
template <class ActorType>
|
||||
class ActorOwn {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
ActorOwn() = default;
|
||||
explicit ActorOwn(ActorId<ActorType> id) : id_(std::move(id)) {
|
||||
}
|
||||
template <class OtherActorType>
|
||||
explicit ActorOwn(ActorId<OtherActorType> id) : id_(std::move(id)) {
|
||||
}
|
||||
template <class OtherActorType>
|
||||
ActorOwn(ActorOwn<OtherActorType> &&other) : id_(other.release()) {
|
||||
}
|
||||
template <class OtherActorType>
|
||||
ActorOwn &operator=(ActorOwn<OtherActorType> &&other) {
|
||||
reset(other.release());
|
||||
return *this;
|
||||
}
|
||||
ActorOwn(ActorOwn &&other) : id_(other.release()) {
|
||||
}
|
||||
ActorOwn &operator=(ActorOwn &&other) {
|
||||
reset(other.release());
|
||||
return *this;
|
||||
}
|
||||
ActorOwn(const ActorOwn &) = delete;
|
||||
ActorOwn &operator=(const ActorOwn &) = delete;
|
||||
~ActorOwn() {
|
||||
reset();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return id_.empty();
|
||||
}
|
||||
bool is_alive() const {
|
||||
return id_.is_alive();
|
||||
}
|
||||
ActorId<ActorType> get() const {
|
||||
return id_;
|
||||
}
|
||||
ActorType &get_actor_unsafe() const {
|
||||
return (*this)->get_actor_unsafe();
|
||||
}
|
||||
ActorId<ActorType> release() {
|
||||
return std::move(id_);
|
||||
}
|
||||
void reset(ActorId<ActorType> other = ActorId<ActorType>()) {
|
||||
static_assert(sizeof(ActorType) > 0, "Can't use ActorOwn with incomplete type");
|
||||
hangup();
|
||||
id_ = std::move(other);
|
||||
}
|
||||
const ActorId<ActorType> *operator->() const {
|
||||
return &id_;
|
||||
}
|
||||
|
||||
detail::ActorRef as_actor_ref() const {
|
||||
CHECK(!empty());
|
||||
return detail::ActorRef(*id_.actor_info_ptr(), 0);
|
||||
}
|
||||
|
||||
private:
|
||||
ActorId<ActorType> id_;
|
||||
void hangup() const {
|
||||
if (empty()) {
|
||||
return;
|
||||
}
|
||||
detail::send_message(as_actor_ref(), detail::ActorMessageCreator::hangup());
|
||||
}
|
||||
};
|
||||
|
||||
template <class ToActorType, class FromActorType>
|
||||
ActorOwn<ToActorType> actor_dynamic_cast(ActorOwn<FromActorType> from) {
|
||||
return ActorOwn<ToActorType>(td::actor::actor_dynamic_cast<ToActorType>(from.release()));
|
||||
}
|
||||
|
||||
} // namespace actor
|
||||
} // namespace td
|
120
tdactor/td/actor/ActorShared.h
Normal file
120
tdactor/td/actor/ActorShared.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/common.h"
|
||||
#include "td/actor/ActorId.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
|
||||
template <class ActorType>
|
||||
class ActorShared {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
ActorShared() = default;
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorId<OtherActorType> id, uint64 token) : id_(std::move(id)), token_(token) {
|
||||
CHECK(token_ != 0);
|
||||
}
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorShared<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
|
||||
}
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorOwn<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
|
||||
}
|
||||
template <class OtherActorType>
|
||||
ActorShared &operator=(ActorShared<OtherActorType> &&other) {
|
||||
reset(other.release(), other.token());
|
||||
}
|
||||
ActorShared(ActorShared &&other) : id_(other.release()), token_(other.token()) {
|
||||
}
|
||||
ActorShared &operator=(ActorShared &&other) {
|
||||
reset(other.release(), other.token());
|
||||
return *this;
|
||||
}
|
||||
ActorShared(const ActorShared &) = delete;
|
||||
ActorShared &operator=(const ActorShared &) = delete;
|
||||
~ActorShared() {
|
||||
reset();
|
||||
}
|
||||
|
||||
uint64 token() const {
|
||||
return token_;
|
||||
}
|
||||
bool empty() const {
|
||||
return id_.empty();
|
||||
}
|
||||
bool is_alive() const {
|
||||
return id_.is_alive();
|
||||
}
|
||||
ActorId<ActorType> get() const {
|
||||
return id_;
|
||||
}
|
||||
ActorId<ActorType> release() {
|
||||
return std::move(id_);
|
||||
}
|
||||
ActorType &get_actor_unsafe() const {
|
||||
return (*this)->get_actor_unsafe();
|
||||
}
|
||||
void reset(ActorId<ActorType> other = ActorId<ActorType>(), uint64 link_token = core::EmptyLinkToken) {
|
||||
static_assert(sizeof(ActorType) > 0, "Can't use ActorShared with incomplete type");
|
||||
hangup();
|
||||
id_ = other;
|
||||
token_ = link_token;
|
||||
}
|
||||
const ActorId<ActorType> *operator->() const {
|
||||
return &id_;
|
||||
}
|
||||
|
||||
detail::ActorRef as_actor_ref() const {
|
||||
CHECK(!empty());
|
||||
return detail::ActorRef(*id_.actor_info_ptr(), token_);
|
||||
}
|
||||
|
||||
private:
|
||||
ActorId<ActorType> id_;
|
||||
uint64 token_;
|
||||
|
||||
void hangup() const {
|
||||
if (empty()) {
|
||||
return;
|
||||
}
|
||||
detail::send_message(as_actor_ref(), detail::ActorMessageCreator::hangup_shared());
|
||||
}
|
||||
};
|
||||
|
||||
template <class ToActorType, class FromActorType>
|
||||
ActorShared<ToActorType> actor_dynamic_cast(ActorShared<FromActorType> from) {
|
||||
return ActorShared<ToActorType>(td::actor::actor_dynamic_cast<ToActorType>(from.release()), from.token());
|
||||
}
|
||||
|
||||
// common interface
|
||||
namespace core { // for ADL
|
||||
template <class SelfT>
|
||||
ActorShared<SelfT> actor_shared(SelfT *self, uint64 id = static_cast<uint64>(-1)) {
|
||||
return ActorShared<SelfT>(actor_id(self), id);
|
||||
}
|
||||
|
||||
inline ActorShared<> actor_shared() {
|
||||
return actor_shared(&core::ActorExecuteContext::get()->actor());
|
||||
}
|
||||
} // namespace core
|
||||
using core::actor_shared;
|
||||
} // namespace actor
|
||||
} // namespace td
|
112
tdactor/td/actor/MultiPromise.cpp
Normal file
112
tdactor/td/actor/MultiPromise.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/MultiPromise.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
class MultiPromiseImpl {
|
||||
public:
|
||||
explicit MultiPromiseImpl(MultiPromise::Options options) : options_(options) {
|
||||
}
|
||||
~MultiPromiseImpl() {
|
||||
for (auto &promise : pending_) {
|
||||
promise.set_value(Unit());
|
||||
}
|
||||
}
|
||||
|
||||
void on_status(Status status) {
|
||||
if (status.is_ok() || options_.ignore_errors) {
|
||||
return;
|
||||
}
|
||||
std::vector<Promise<>> promises;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (pending_error_.is_ok()) {
|
||||
pending_error_ = status.clone();
|
||||
std::swap(promises, pending_);
|
||||
} else {
|
||||
CHECK(pending_.empty());
|
||||
}
|
||||
}
|
||||
for (auto &promise : promises) {
|
||||
promise.set_error(status.clone());
|
||||
}
|
||||
}
|
||||
void add_promise(Promise<> promise) {
|
||||
if (options_.ignore_errors) {
|
||||
pending_.push_back(std::move(promise));
|
||||
}
|
||||
Status status;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (pending_error_.is_error()) {
|
||||
status = pending_error_.clone();
|
||||
} else {
|
||||
pending_.push_back(std::move(promise));
|
||||
}
|
||||
}
|
||||
if (status.is_error()) {
|
||||
promise.set_error(std::move(status));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::vector<Promise<>> pending_;
|
||||
MultiPromise::Options options_;
|
||||
Status pending_error_;
|
||||
};
|
||||
} // namespace detail
|
||||
void MultiPromise::InitGuard::add_promise(Promise<> promise) {
|
||||
impl_->add_promise(std::move(promise));
|
||||
}
|
||||
Promise<> MultiPromise::InitGuard::get_promise() {
|
||||
return [impl = impl_](Result<Unit> result) {
|
||||
if (result.is_ok()) {
|
||||
impl->on_status(Status::OK());
|
||||
} else {
|
||||
impl->on_status(result.move_as_error());
|
||||
}
|
||||
};
|
||||
}
|
||||
bool MultiPromise::InitGuard::empty() const {
|
||||
return !impl_;
|
||||
}
|
||||
MultiPromise::InitGuard::operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
MultiPromise::InitGuard MultiPromise::init_guard() {
|
||||
CHECK(!impl_.lock());
|
||||
auto impl = std::make_shared<Impl>(options_);
|
||||
impl_ = impl;
|
||||
return InitGuard(std::move(impl));
|
||||
}
|
||||
MultiPromise::InitGuard MultiPromise::add_promise_or_init(Promise<> promise) {
|
||||
auto impl = impl_.lock();
|
||||
if (!impl) {
|
||||
auto guard = init_guard();
|
||||
guard.add_promise(std::move(promise));
|
||||
return guard;
|
||||
}
|
||||
impl->add_promise(std::move(promise));
|
||||
return {};
|
||||
}
|
||||
} // namespace td
|
65
tdactor/td/actor/MultiPromise.h
Normal file
65
tdactor/td/actor/MultiPromise.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
class MultiPromiseImpl;
|
||||
}
|
||||
|
||||
class MultiPromise {
|
||||
using Impl = detail::MultiPromiseImpl;
|
||||
|
||||
public:
|
||||
struct Options {
|
||||
Options() {
|
||||
}
|
||||
bool ignore_errors{false};
|
||||
};
|
||||
explicit MultiPromise(Options options = Options{}) : options_(options) {
|
||||
}
|
||||
|
||||
struct InitGuard {
|
||||
public:
|
||||
InitGuard() = default;
|
||||
InitGuard(std::shared_ptr<Impl> impl) : impl_(std::move(impl)) {
|
||||
}
|
||||
InitGuard(InitGuard &&other) = default;
|
||||
InitGuard &operator=(InitGuard &&other) = default;
|
||||
InitGuard(const InitGuard &other) = delete;
|
||||
InitGuard &operator=(const InitGuard &other) = delete;
|
||||
|
||||
void add_promise(Promise<> promise);
|
||||
Promise<> get_promise();
|
||||
bool empty() const;
|
||||
explicit operator bool() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
TD_WARN_UNUSED_RESULT InitGuard init_guard();
|
||||
TD_WARN_UNUSED_RESULT InitGuard add_promise_or_init(Promise<> promise);
|
||||
|
||||
private:
|
||||
Options options_;
|
||||
std::weak_ptr<Impl> impl_;
|
||||
};
|
||||
} // namespace td
|
336
tdactor/td/actor/PromiseFuture.h
Normal file
336
tdactor/td/actor/PromiseFuture.h
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/invoke.h" // for tuple_for_each
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct GetArg : public GetArg<decltype(&T::operator())> {};
|
||||
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg)> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
template <class C, class R, class Arg>
|
||||
class GetArg<R (C::*)(Arg) const> {
|
||||
public:
|
||||
using type = Arg;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
|
||||
|
||||
template <class T>
|
||||
struct DropResult {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct DropResult<Result<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using drop_result_t = typename DropResult<T>::type;
|
||||
} // namespace detail
|
||||
|
||||
template <class T = Unit>
|
||||
class PromiseInterface {
|
||||
public:
|
||||
using ValueType = T;
|
||||
PromiseInterface() = default;
|
||||
PromiseInterface(const PromiseInterface &) = delete;
|
||||
PromiseInterface &operator=(const PromiseInterface &) = delete;
|
||||
PromiseInterface(PromiseInterface &&) = default;
|
||||
PromiseInterface &operator=(PromiseInterface &&) = default;
|
||||
virtual ~PromiseInterface() = default;
|
||||
|
||||
virtual void set_value(T &&value) {
|
||||
set_result(std::move(value));
|
||||
}
|
||||
virtual void set_error(Status &&error) {
|
||||
set_result(std::move(error));
|
||||
}
|
||||
virtual void set_result(Result<T> &&result) {
|
||||
if (result.is_ok()) {
|
||||
set_value(result.move_as_ok());
|
||||
} else {
|
||||
set_error(result.move_as_error());
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(T &&value) {
|
||||
set_value(std::move(value));
|
||||
}
|
||||
void operator()(Status &&error) {
|
||||
set_error(std::move(error));
|
||||
}
|
||||
void operator()(Result<T> &&result) {
|
||||
set_result(std::move(result));
|
||||
}
|
||||
};
|
||||
template <class T = Unit>
|
||||
class Promise;
|
||||
|
||||
constexpr std::false_type is_promise_interface(...) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface(const PromiseInterface<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface(const Promise<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr bool is_promise_interface() {
|
||||
return decltype(is_promise_interface(std::declval<F>()))::value;
|
||||
}
|
||||
|
||||
constexpr std::false_type is_promise_interface_ptr(...) {
|
||||
return {};
|
||||
}
|
||||
template <class T>
|
||||
constexpr std::true_type is_promise_interface_ptr(const unique_ptr<T> &promise) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr bool is_promise_interface_ptr() {
|
||||
return decltype(is_promise_interface_ptr(std::declval<F>()))::value;
|
||||
}
|
||||
template <class ValueT, class FunctionT>
|
||||
class LambdaPromise : public PromiseInterface<ValueT> {
|
||||
public:
|
||||
void set_value(ValueT &&value) override {
|
||||
CHECK(has_lambda_.get());
|
||||
do_ok(std::move(value));
|
||||
has_lambda_ = false;
|
||||
}
|
||||
void set_error(Status &&error) override {
|
||||
CHECK(has_lambda_.get());
|
||||
do_error(std::move(error));
|
||||
has_lambda_ = false;
|
||||
}
|
||||
|
||||
LambdaPromise(const LambdaPromise &other) = delete;
|
||||
LambdaPromise &operator=(const LambdaPromise &other) = delete;
|
||||
LambdaPromise(LambdaPromise &&other) = default;
|
||||
LambdaPromise &operator=(LambdaPromise &&other) = default;
|
||||
~LambdaPromise() override {
|
||||
if (has_lambda_.get()) {
|
||||
do_error(Status::Error("Lost promise"));
|
||||
}
|
||||
}
|
||||
|
||||
template <class FromOkT>
|
||||
explicit LambdaPromise(FromOkT &&ok) : ok_(std::forward<FromOkT>(ok)), has_lambda_(true) {
|
||||
}
|
||||
|
||||
private:
|
||||
FunctionT ok_;
|
||||
MovableValue<bool> has_lambda_{false};
|
||||
|
||||
template <class F = FunctionT>
|
||||
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_error(Status &&status) {
|
||||
ok_(Result<ValueT>(std::move(status)));
|
||||
}
|
||||
template <class Y, class F = FunctionT>
|
||||
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_error(Y &&status) {
|
||||
ok_(Auto());
|
||||
}
|
||||
template <class F = FunctionT>
|
||||
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&result) {
|
||||
ok_(Result<ValueT>(std::move(result)));
|
||||
}
|
||||
template <class F = FunctionT>
|
||||
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&result) {
|
||||
ok_(std::move(result));
|
||||
}
|
||||
};
|
||||
|
||||
template <class T = void, class F = void, std::enable_if_t<std::is_same<T, void>::value, bool> has_t = false>
|
||||
auto lambda_promise(F &&f) {
|
||||
return LambdaPromise<detail::drop_result_t<detail::get_arg_t<std::decay_t<F>>>, std::decay_t<F>>(std::forward<F>(f));
|
||||
}
|
||||
template <class T = void, class F = void, std::enable_if_t<!std::is_same<T, void>::value, bool> has_t = true>
|
||||
auto lambda_promise(F &&f) {
|
||||
return LambdaPromise<T, std::decay_t<F>>(std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<is_promise_interface<F>(), bool> from_promise_inerface = true>
|
||||
auto &&promise_interface(F &&f) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<!is_promise_interface<F>(), bool> from_promise_inerface = false>
|
||||
auto promise_interface(F &&f) {
|
||||
return lambda_promise<T>(std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class T, class F, std::enable_if_t<is_promise_interface_ptr<F>(), bool> from_promise_inerface = true>
|
||||
auto promise_interface_ptr(F &&f) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
template <class T, class F, std::enable_if_t<!is_promise_interface_ptr<F>(), bool> from_promise_inerface = false>
|
||||
auto promise_interface_ptr(F &&f) {
|
||||
return std::make_unique<std::decay_t<decltype(promise_interface<T>(std::forward<F>(f)))>>(
|
||||
promise_interface<T>(std::forward<F>(f)));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class Promise {
|
||||
public:
|
||||
using ArgT = T;
|
||||
void set_value(T &&value) {
|
||||
if (!promise_) {
|
||||
return;
|
||||
}
|
||||
promise_->set_value(std::move(value));
|
||||
promise_.reset();
|
||||
}
|
||||
void set_error(Status &&error) {
|
||||
if (!promise_) {
|
||||
return;
|
||||
}
|
||||
promise_->set_error(std::move(error));
|
||||
promise_.reset();
|
||||
}
|
||||
void set_result(Result<T> &&result) {
|
||||
if (!promise_) {
|
||||
return;
|
||||
}
|
||||
promise_->set_result(std::move(result));
|
||||
promise_.reset();
|
||||
}
|
||||
template <class S>
|
||||
void operator()(S &&result) {
|
||||
if (!promise_) {
|
||||
return;
|
||||
}
|
||||
promise_->operator()(std::forward<S>(result));
|
||||
promise_.reset();
|
||||
}
|
||||
void reset() {
|
||||
promise_.reset();
|
||||
}
|
||||
std::unique_ptr<PromiseInterface<T>> release() {
|
||||
return std::move(promise_);
|
||||
}
|
||||
|
||||
Promise() = default;
|
||||
explicit Promise(std::unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
|
||||
Promise &operator=(Promise &&) = default;
|
||||
Promise(Promise &&) = default;
|
||||
template <class F>
|
||||
Promise(F &&f) : promise_(promise_interface_ptr<T>(std::forward<F>(f))) {
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return static_cast<bool>(promise_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<PromiseInterface<T>> promise_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
auto make_promise(F &&f) {
|
||||
using ValueT = detail::drop_result_t<detail::get_arg_t<F>>;
|
||||
return Promise<ValueT>(promise_interface_ptr(std::forward<F>(f)));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
template <class... ArgsT>
|
||||
class JoinPromise : public PromiseInterface<Unit> {
|
||||
public:
|
||||
explicit JoinPromise(ArgsT &&... arg) : promises_(std::forward<ArgsT>(arg)...) {
|
||||
}
|
||||
void set_value(Unit &&) override {
|
||||
tuple_for_each(promises_, [](auto &promise) { promise.set_value(Unit()); });
|
||||
}
|
||||
void set_error(Status &&error) override {
|
||||
tuple_for_each(promises_, [&error](auto &promise) { promise.set_error(error.clone()); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<std::decay_t<ArgsT>...> promises_;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
class PromiseCreator {
|
||||
public:
|
||||
struct Ignore {
|
||||
void operator()(Status &&error) {
|
||||
error.ignore();
|
||||
}
|
||||
};
|
||||
|
||||
template <class OkT>
|
||||
static auto lambda(OkT &&ok) {
|
||||
return lambda_promise(std::forward<OkT>(ok));
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
static Promise<> join(ArgsT &&... args) {
|
||||
return Promise<>(std::make_unique<detail::JoinPromise<ArgsT...>>(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
template <class T = Unit>
|
||||
class SafePromise {
|
||||
public:
|
||||
SafePromise(Promise<T> promise, Result<T> result) : promise_(std::move(promise)), result_(std::move(result)) {
|
||||
}
|
||||
SafePromise(const SafePromise &other) = delete;
|
||||
SafePromise &operator=(const SafePromise &other) = delete;
|
||||
SafePromise(SafePromise &&other) = default;
|
||||
SafePromise &operator=(SafePromise &&other) = default;
|
||||
~SafePromise() {
|
||||
if (promise_) {
|
||||
promise_.set_result(std::move(result_));
|
||||
}
|
||||
}
|
||||
Promise<T> release() {
|
||||
return std::move(promise_);
|
||||
}
|
||||
operator Promise<T>() && {
|
||||
return release();
|
||||
}
|
||||
|
||||
private:
|
||||
Promise<T> promise_;
|
||||
Result<T> result_;
|
||||
};
|
||||
} // namespace td
|
165
tdactor/td/actor/actor.h
Normal file
165
tdactor/td/actor/actor.h
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/common.h"
|
||||
#include "td/actor/ActorId.h"
|
||||
#include "td/actor/ActorOwn.h"
|
||||
#include "td/actor/ActorShared.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
|
||||
template <class T, class... ArgsT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<T> create_actor(ActorOptions options, ArgsT &&... args) {
|
||||
return ActorOwn<T>(ActorId<T>::create(options, std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class T, class... ArgsT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<T> create_actor(Slice name, ArgsT &&... args) {
|
||||
return ActorOwn<T>(ActorId<T>::create(ActorOptions().with_name(name), std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
#define SEND_CLOSURE_LATER 1
|
||||
#ifndef SEND_CLOSURE_LATER
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count == sizeof...(ArgsT), bool> with_promise = false>
|
||||
void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure(id.as_actor_ref(), function, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count != sizeof...(ArgsT), bool> with_promise = true>
|
||||
void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure_with_promise(id.as_actor_ref(),
|
||||
call_n_arguments<argument_count>(
|
||||
[&function](auto &&... nargs) {
|
||||
return create_immediate_closure(function,
|
||||
std::forward<decltype(nargs)>(nargs)...);
|
||||
},
|
||||
std::forward<ArgsT>(args)...),
|
||||
get_last_argument(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count == sizeof...(ArgsT), bool> with_promise = false>
|
||||
void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure_later(id.as_actor_ref(), function, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count != sizeof...(ArgsT), bool> with_promise = true>
|
||||
void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure_with_promise_later(id.as_actor_ref(),
|
||||
call_n_arguments<argument_count>(
|
||||
[&function](auto &&... nargs) {
|
||||
return create_delayed_closure(function,
|
||||
std::forward<decltype(nargs)>(nargs)...);
|
||||
},
|
||||
std::forward<ArgsT>(args)...),
|
||||
get_last_argument(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template <typename ActorIdT, typename FunctionT, typename... ArgsT>
|
||||
bool send_closure_bool(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
send_closure(std::forward<ActorIdT>(actor_id), function, std::forward<ArgsT>(args)...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count == sizeof...(ArgsT), bool> with_promise = false>
|
||||
void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure_later(id.as_actor_ref(), function, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count != sizeof...(ArgsT), bool> with_promise = true>
|
||||
void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_closure_with_promise_later(id.as_actor_ref(),
|
||||
call_n_arguments<argument_count>(
|
||||
[&function](auto &&... nargs) {
|
||||
return create_delayed_closure(function,
|
||||
std::forward<decltype(nargs)>(nargs)...);
|
||||
},
|
||||
std::forward<ArgsT>(args)...),
|
||||
get_last_argument(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <typename ActorIdT, typename FunctionT, typename... ArgsT>
|
||||
bool send_closure_later_bool(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
send_closure_later(std::forward<ActorIdT>(actor_id), function, std::forward<ArgsT>(args)...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class ActorIdT, class... ArgsT>
|
||||
void send_lambda(ActorIdT &&actor_id, ArgsT &&... args) {
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_lambda(id.as_actor_ref(), std::forward<ArgsT>(args)...);
|
||||
}
|
||||
template <class ActorIdT, class... ArgsT>
|
||||
void send_lambda_later(ActorIdT &&actor_id, ArgsT &&... args) {
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_lambda_later(id.as_actor_ref(), std::forward<ArgsT>(args)...);
|
||||
}
|
||||
template <class ActorIdT>
|
||||
void send_signals(ActorIdT &&actor_id, ActorSignals signals) {
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_signals(id.as_actor_ref(), signals);
|
||||
}
|
||||
template <class ActorIdT>
|
||||
void send_signals_later(ActorIdT &&actor_id, ActorSignals signals) {
|
||||
ActorIdT id = std::forward<ActorIdT>(actor_id);
|
||||
detail::send_signals_later(id.as_actor_ref(), signals);
|
||||
}
|
||||
} // namespace actor
|
||||
} // namespace td
|
379
tdactor/td/actor/common.h
Normal file
379
tdactor/td/actor/common.h
Normal file
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/core/Actor.h"
|
||||
#include "td/actor/core/ActorSignals.h"
|
||||
#include "td/actor/core/SchedulerId.h"
|
||||
#include "td/actor/core/SchedulerContext.h"
|
||||
#include "td/actor/core/Scheduler.h"
|
||||
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/Timer.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
using core::ActorOptions;
|
||||
|
||||
// Replacement for core::ActorSignals. Easier to use and do not allow internal signals
|
||||
class ActorSignals {
|
||||
public:
|
||||
static ActorSignals pause() {
|
||||
return ActorSignals(core::ActorSignals::one(core::ActorSignals::Pause));
|
||||
}
|
||||
static ActorSignals kill() {
|
||||
return ActorSignals(core::ActorSignals::one(core::ActorSignals::Kill));
|
||||
}
|
||||
static ActorSignals wakeup() {
|
||||
return ActorSignals(core::ActorSignals::one(core::ActorSignals::Wakeup));
|
||||
}
|
||||
friend ActorSignals operator|(ActorSignals a, ActorSignals b) {
|
||||
a.raw_.add_signals(b.raw_);
|
||||
return a;
|
||||
}
|
||||
ActorSignals() = default;
|
||||
|
||||
core::ActorSignals raw() const {
|
||||
return raw_;
|
||||
}
|
||||
|
||||
private:
|
||||
ActorSignals(core::ActorSignals raw) : raw_(raw) {
|
||||
}
|
||||
core::ActorSignals raw_;
|
||||
};
|
||||
|
||||
// TODO: proper interface
|
||||
using core::Actor;
|
||||
using core::SchedulerContext;
|
||||
using core::SchedulerId;
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
struct NodeInfo {
|
||||
NodeInfo(size_t cpu_threads) : cpu_threads_(cpu_threads) {
|
||||
}
|
||||
NodeInfo(size_t cpu_threads, size_t io_threads) : cpu_threads_(cpu_threads), io_threads_(io_threads) {
|
||||
}
|
||||
size_t cpu_threads_;
|
||||
size_t io_threads_{1};
|
||||
};
|
||||
|
||||
enum Mode { Running, Paused };
|
||||
Scheduler(std::vector<NodeInfo> infos, Mode mode = Paused) : infos_(std::move(infos)) {
|
||||
init();
|
||||
if (mode == Running) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
~Scheduler() {
|
||||
stop();
|
||||
}
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
Scheduler(Scheduler &&) = delete;
|
||||
Scheduler &operator=(const Scheduler &) = delete;
|
||||
Scheduler &operator=(Scheduler &&) = delete;
|
||||
|
||||
void start() {
|
||||
if (is_started_) {
|
||||
return;
|
||||
}
|
||||
is_started_ = true;
|
||||
for (size_t it = 0; it < schedulers_.size(); it++) {
|
||||
auto &scheduler = schedulers_[it];
|
||||
scheduler->start();
|
||||
if (it != 0) {
|
||||
auto thread = td::thread([&] {
|
||||
while (scheduler->run(10)) {
|
||||
}
|
||||
});
|
||||
thread.set_name(PSLICE() << "#" << it << ":io");
|
||||
thread.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool run() {
|
||||
start();
|
||||
while (schedulers_[0]->run(10)) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool run(double timeout) {
|
||||
start();
|
||||
return schedulers_[0]->run(timeout);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void run_in_context(F &&f) {
|
||||
schedulers_[0]->run_in_context(std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void run_in_context_external(F &&f) {
|
||||
schedulers_[0]->run_in_context_external(std::forward<F>(f));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!group_info_) {
|
||||
return;
|
||||
}
|
||||
if (!is_started_) {
|
||||
start();
|
||||
}
|
||||
schedulers_[0]->stop();
|
||||
run();
|
||||
core::Scheduler::close_scheduler_group(*group_info_);
|
||||
group_info_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<NodeInfo> infos_{td::thread::hardware_concurrency()};
|
||||
std::shared_ptr<core::SchedulerGroupInfo> group_info_;
|
||||
std::vector<td::unique_ptr<core::Scheduler>> schedulers_;
|
||||
bool is_started_{false};
|
||||
|
||||
void init() {
|
||||
CHECK(infos_.size() < 256);
|
||||
CHECK(!group_info_);
|
||||
group_info_ = std::make_shared<core::SchedulerGroupInfo>(infos_.size());
|
||||
td::uint8 id = 0;
|
||||
for (const auto &info : infos_) {
|
||||
schedulers_.emplace_back(td::make_unique<core::Scheduler>(group_info_, core::SchedulerId{id}, info.cpu_threads_));
|
||||
id++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Some helper functions. Not part of public interface and not part
|
||||
// of namespace core
|
||||
namespace detail {
|
||||
template <class LambdaT>
|
||||
class ActorMessageLambda : public core::ActorMessageImpl {
|
||||
public:
|
||||
template <class FromLambdaT>
|
||||
explicit ActorMessageLambda(FromLambdaT &&lambda) : lambda_(std::forward<FromLambdaT>(lambda)) {
|
||||
}
|
||||
void run() override {
|
||||
//delivery_warning_.reset();
|
||||
lambda_();
|
||||
}
|
||||
|
||||
private:
|
||||
LambdaT lambda_;
|
||||
//PerfWarningTimer delivery_warning_{"Long message delivery", 2};
|
||||
};
|
||||
|
||||
class ActorMessageCreator {
|
||||
public:
|
||||
template <class F>
|
||||
static auto lambda(F &&f) {
|
||||
return core::ActorMessage(std::make_unique<ActorMessageLambda<std::decay_t<F>>>(std::forward<F>(f)));
|
||||
}
|
||||
|
||||
static auto hangup() {
|
||||
return core::ActorMessage(std::make_unique<core::ActorMessageHangup>());
|
||||
}
|
||||
|
||||
static auto hangup_shared() {
|
||||
return core::ActorMessage(std::make_unique<core::ActorMessageHangupShared>());
|
||||
}
|
||||
|
||||
// Use faster allocation?
|
||||
};
|
||||
struct ActorRef {
|
||||
ActorRef(core::ActorInfo &actor_info, uint64 link_token = core::EmptyLinkToken)
|
||||
: actor_info(actor_info), link_token(link_token) {
|
||||
}
|
||||
|
||||
core::ActorInfo &actor_info;
|
||||
uint64 link_token;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
T ¤t_actor() {
|
||||
return static_cast<T &>(core::ActorExecuteContext::get()->actor());
|
||||
}
|
||||
|
||||
inline void send_message(core::ActorInfo &actor_info, core::ActorMessage message) {
|
||||
auto scheduler_context_ptr = core::SchedulerContext::get();
|
||||
if (scheduler_context_ptr == nullptr) {
|
||||
LOG(ERROR) << "send to actor is silently ignored";
|
||||
return;
|
||||
}
|
||||
auto &scheduler_context = *scheduler_context_ptr;
|
||||
core::ActorExecutor executor(actor_info, scheduler_context,
|
||||
core::ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
|
||||
executor.send(std::move(message));
|
||||
}
|
||||
|
||||
inline void send_message(ActorRef actor_ref, core::ActorMessage message) {
|
||||
message.set_link_token(actor_ref.link_token);
|
||||
send_message(actor_ref.actor_info, std::move(message));
|
||||
}
|
||||
inline void send_message_later(core::ActorInfo &actor_info, core::ActorMessage message) {
|
||||
auto scheduler_context_ptr = core::SchedulerContext::get();
|
||||
if (scheduler_context_ptr == nullptr) {
|
||||
LOG(ERROR) << "send to actor is silently ignored";
|
||||
return;
|
||||
}
|
||||
auto &scheduler_context = *scheduler_context_ptr;
|
||||
core::ActorExecutor executor(actor_info, scheduler_context,
|
||||
core::ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
|
||||
message.set_big();
|
||||
executor.send(std::move(message));
|
||||
}
|
||||
|
||||
inline void send_message_later(ActorRef actor_ref, core::ActorMessage message) {
|
||||
message.set_link_token(actor_ref.link_token);
|
||||
send_message_later(actor_ref.actor_info, std::move(message));
|
||||
}
|
||||
|
||||
template <class ExecuteF, class ToMessageF>
|
||||
void send_immediate(ActorRef actor_ref, ExecuteF &&execute, ToMessageF &&to_message) {
|
||||
auto scheduler_context_ptr = core::SchedulerContext::get();
|
||||
if (scheduler_context_ptr == nullptr) {
|
||||
LOG(ERROR) << "send to actor is silently ignored";
|
||||
return;
|
||||
}
|
||||
auto &scheduler_context = *scheduler_context_ptr;
|
||||
core::ActorExecutor executor(actor_ref.actor_info, scheduler_context,
|
||||
core::ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
|
||||
if (executor.can_send_immediate()) {
|
||||
return executor.send_immediate(execute, actor_ref.link_token);
|
||||
}
|
||||
auto message = to_message();
|
||||
message.set_link_token(actor_ref.link_token);
|
||||
executor.send(std::move(message));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void send_lambda(ActorRef actor_ref, F &&lambda) {
|
||||
send_immediate(actor_ref, lambda, [&lambda]() mutable { return ActorMessageCreator::lambda(std::move(lambda)); });
|
||||
}
|
||||
template <class F>
|
||||
void send_lambda_later(ActorRef actor_ref, F &&lambda) {
|
||||
send_message_later(actor_ref, ActorMessageCreator::lambda(std::move(lambda)));
|
||||
}
|
||||
|
||||
template <class ClosureT>
|
||||
void send_closure_impl(ActorRef actor_ref, ClosureT &&closure) {
|
||||
using ActorType = typename ClosureT::ActorType;
|
||||
send_immediate(
|
||||
actor_ref, [&closure]() mutable { closure.run(¤t_actor<ActorType>()); },
|
||||
[&closure]() mutable {
|
||||
return ActorMessageCreator::lambda(
|
||||
[closure = to_delayed_closure(std::move(closure))]() mutable { closure.run(¤t_actor<ActorType>()); });
|
||||
});
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void send_closure(ActorRef actor_ref, ArgsT &&... args) {
|
||||
send_closure_impl(actor_ref, create_immediate_closure(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class ClosureT>
|
||||
void send_closure_later_impl(ActorRef actor_ref, ClosureT &&closure) {
|
||||
using ActorType = typename ClosureT::ActorType;
|
||||
send_message_later(actor_ref,
|
||||
ActorMessageCreator::lambda([closure = to_delayed_closure(std::move(closure))]() mutable {
|
||||
closure.run(¤t_actor<ActorType>());
|
||||
}));
|
||||
}
|
||||
|
||||
template <class ClosureT, class PromiseT>
|
||||
void send_closure_with_promise(ActorRef actor_ref, ClosureT &&closure, PromiseT &&promise) {
|
||||
using ActorType = typename ClosureT::ActorType;
|
||||
using ResultType = decltype(closure.run(¤t_actor<ActorType>()));
|
||||
auto &&promise_i = promise_interface<ResultType>(std::forward<PromiseT>(promise));
|
||||
send_immediate(
|
||||
actor_ref, [&closure, &promise = promise_i]() mutable { promise(closure.run(¤t_actor<ActorType>())); },
|
||||
[&closure, &promise = promise_i]() mutable {
|
||||
return ActorMessageCreator::lambda(
|
||||
[closure = to_delayed_closure(std::move(closure)), promise = std::move(promise)]() mutable {
|
||||
promise(closure.run(¤t_actor<ActorType>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <class ClosureT, class PromiseT>
|
||||
void send_closure_with_promise_later(ActorRef actor_ref, ClosureT &&closure, PromiseT &&promise) {
|
||||
using ActorType = typename ClosureT::ActorType;
|
||||
using ResultType = decltype(closure.run(¤t_actor<ActorType>()));
|
||||
send_message_later(
|
||||
actor_ref,
|
||||
ActorMessageCreator::lambda([closure = to_delayed_closure(std::move(closure)),
|
||||
promise = promise_interface<ResultType>(std::forward<PromiseT>(promise))]() mutable {
|
||||
promise(closure.run(¤t_actor<ActorType>()));
|
||||
}));
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void send_closure_later(ActorRef actor_ref, ArgsT &&... args) {
|
||||
send_closure_later_impl(actor_ref, create_delayed_closure(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
inline void send_signals(ActorRef actor_ref, ActorSignals signals) {
|
||||
auto scheduler_context_ptr = core::SchedulerContext::get();
|
||||
if (scheduler_context_ptr == nullptr) {
|
||||
LOG(ERROR) << "send to actor is silently ignored";
|
||||
return;
|
||||
}
|
||||
auto &scheduler_context = *scheduler_context_ptr;
|
||||
core::ActorExecutor executor(actor_ref.actor_info, scheduler_context,
|
||||
core::ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
|
||||
if (executor.can_send_immediate()) {
|
||||
return executor.send_immediate(signals.raw());
|
||||
}
|
||||
executor.send(signals.raw());
|
||||
}
|
||||
|
||||
inline void send_signals_later(ActorRef actor_ref, ActorSignals signals) {
|
||||
auto scheduler_context_ptr = core::SchedulerContext::get();
|
||||
if (scheduler_context_ptr == nullptr) {
|
||||
LOG(ERROR) << "send to actor is silently ignored";
|
||||
return;
|
||||
}
|
||||
auto &scheduler_context = *scheduler_context_ptr;
|
||||
core::ActorExecutor executor(actor_ref.actor_info, scheduler_context,
|
||||
core::ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
|
||||
executor.send((signals | ActorSignals::pause()).raw());
|
||||
}
|
||||
|
||||
inline void register_actor_info_ptr(core::ActorInfoPtr actor_info_ptr) {
|
||||
auto state = actor_info_ptr->state().get_flags_unsafe();
|
||||
core::SchedulerContext::get()->add_to_queue(std::move(actor_info_ptr), state.get_scheduler_id(), !state.is_shared());
|
||||
}
|
||||
|
||||
template <class T, class... ArgsT>
|
||||
core::ActorInfoPtr create_actor(core::ActorOptions &options, ArgsT &&... args) {
|
||||
auto *scheduler_context = core::SchedulerContext::get();
|
||||
if (!options.has_scheduler()) {
|
||||
options.on_scheduler(scheduler_context->get_scheduler_id());
|
||||
}
|
||||
auto res =
|
||||
scheduler_context->get_actor_info_creator().create(std::make_unique<T>(std::forward<ArgsT>(args)...), options);
|
||||
register_actor_info_ptr(res);
|
||||
return res;
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace actor
|
||||
} // namespace td
|
108
tdactor/td/actor/core/Actor.h
Normal file
108
tdactor/td/actor/core/Actor.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
|
||||
#include "td/utils/SharedObjectPool.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class Actor {
|
||||
public:
|
||||
Actor() = default;
|
||||
Actor(const Actor &) = delete;
|
||||
Actor &operator=(const Actor &) = delete;
|
||||
Actor(Actor &&other) = delete;
|
||||
Actor &operator=(Actor &&other) = delete;
|
||||
virtual ~Actor() = default;
|
||||
|
||||
void set_actor_info_ptr(ActorInfoPtr actor_info_ptr) {
|
||||
actor_info_ptr_ = std::move(actor_info_ptr);
|
||||
}
|
||||
const ActorInfoPtr &get_actor_info_ptr() const {
|
||||
return actor_info_ptr_;
|
||||
}
|
||||
|
||||
td::Unit dummy() {
|
||||
return td::Unit();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Signal handlers
|
||||
virtual void start_up() { // StartUp signal handler
|
||||
yield();
|
||||
}
|
||||
virtual void tear_down() { // TearDown signal handler (or Kill)
|
||||
//noop
|
||||
}
|
||||
virtual void hangup() { // HangUp signal handler
|
||||
stop();
|
||||
}
|
||||
virtual void hangup_shared() { // HangUp signal handler
|
||||
loop();
|
||||
}
|
||||
virtual void wake_up() { // WakeUp signal handler
|
||||
loop();
|
||||
}
|
||||
virtual void alarm() { // Alarm signal handler
|
||||
loop();
|
||||
}
|
||||
|
||||
virtual void loop() { // default handler
|
||||
//noop
|
||||
}
|
||||
|
||||
// Useful functions
|
||||
void yield() { // send wakeup signal to itself
|
||||
ActorExecuteContext::get()->set_yield();
|
||||
}
|
||||
void stop() { // send Kill signal to itself
|
||||
ActorExecuteContext::get()->set_stop();
|
||||
}
|
||||
Timestamp &alarm_timestamp() {
|
||||
return ActorExecuteContext::get()->alarm_timestamp();
|
||||
}
|
||||
Timestamp get_alarm_timestamp() {
|
||||
return ActorExecuteContext::get()->get_alarm_timestamp();
|
||||
}
|
||||
|
||||
CSlice get_name() {
|
||||
return actor_info_ptr_->get_name();
|
||||
}
|
||||
uint64 get_link_token() {
|
||||
return ActorExecuteContext::get()->get_link_token();
|
||||
}
|
||||
|
||||
//set context that will be inherited by all childrens
|
||||
//void set_context(std::shared_ptr<ActorContext> context);
|
||||
|
||||
//void do_stop(); // process Kill signal immediately
|
||||
|
||||
private:
|
||||
friend class ActorExecutor;
|
||||
friend class ActorMessageHangup;
|
||||
friend class ActorMessageHangupShared;
|
||||
|
||||
ActorInfoPtr actor_info_ptr_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
101
tdactor/td/actor/core/ActorExecuteContext.h
Normal file
101
tdactor/td/actor/core/ActorExecuteContext.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/Context.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class Actor;
|
||||
|
||||
enum : uint64 { EmptyLinkToken = std::numeric_limits<uint64>::max() };
|
||||
|
||||
class ActorExecuteContext : public Context<ActorExecuteContext> {
|
||||
public:
|
||||
explicit ActorExecuteContext(Actor *actor, Timestamp alarm_timestamp = Timestamp::never())
|
||||
: actor_(actor), alarm_timestamp_(alarm_timestamp) {
|
||||
}
|
||||
void set_actor(Actor *actor) {
|
||||
actor_ = actor;
|
||||
}
|
||||
Actor &actor() const {
|
||||
CHECK(actor_);
|
||||
return *actor_;
|
||||
}
|
||||
bool has_flags() const {
|
||||
return flags_ != 0;
|
||||
}
|
||||
bool has_immediate_flags() const {
|
||||
return (flags_ & ~(1 << Alarm)) != 0;
|
||||
}
|
||||
void set_stop() {
|
||||
flags_ |= 1 << Stop;
|
||||
}
|
||||
bool get_stop() const {
|
||||
return (flags_ & (1 << Stop)) != 0;
|
||||
}
|
||||
void set_pause() {
|
||||
flags_ |= 1 << Pause;
|
||||
}
|
||||
bool get_pause() const {
|
||||
return (flags_ & (1 << Pause)) != 0;
|
||||
}
|
||||
void clear_actor() {
|
||||
actor_ = nullptr;
|
||||
}
|
||||
void set_link_token(uint64 link_token) {
|
||||
link_token_ = link_token;
|
||||
}
|
||||
uint64 get_link_token() const {
|
||||
return link_token_;
|
||||
}
|
||||
Timestamp &alarm_timestamp() {
|
||||
flags_ |= 1 << Alarm;
|
||||
return alarm_timestamp_;
|
||||
}
|
||||
bool get_alarm_flag() const {
|
||||
return (flags_ & (1 << Alarm)) != 0;
|
||||
}
|
||||
Timestamp get_alarm_timestamp() const {
|
||||
return alarm_timestamp_;
|
||||
}
|
||||
void set_yield() {
|
||||
flags_ |= 1 << Yield;
|
||||
}
|
||||
bool get_yield() {
|
||||
return (flags_ & (1 << Yield)) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Actor *actor_;
|
||||
uint32 flags_{0};
|
||||
uint64 link_token_{EmptyLinkToken};
|
||||
Timestamp alarm_timestamp_;
|
||||
enum { Stop, Pause, Alarm, Yield };
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
281
tdactor/td/actor/core/ActorExecutor.cpp
Normal file
281
tdactor/td/actor/core/ActorExecutor.cpp
Normal file
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/core/ActorExecutor.h"
|
||||
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
void ActorExecutor::send_immediate(ActorMessage message) {
|
||||
CHECK(can_send_immediate());
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
if (message.is_big()) {
|
||||
actor_info_.mailbox().reader().delay(std::move(message));
|
||||
pending_signals_.add_signal(ActorSignals::Message);
|
||||
actor_execute_context_.set_pause();
|
||||
return;
|
||||
}
|
||||
actor_execute_context_.set_link_token(message.get_link_token());
|
||||
message.run();
|
||||
}
|
||||
|
||||
void ActorExecutor::send_immediate(ActorSignals signals) {
|
||||
CHECK(can_send_immediate());
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
SCOPE_EXIT {
|
||||
pending_signals_.add_signals(signals);
|
||||
};
|
||||
while (flush_one_signal(signals) && !actor_execute_context_.has_immediate_flags()) {
|
||||
}
|
||||
}
|
||||
|
||||
void ActorExecutor::send(ActorMessage message) {
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
if (can_send_immediate()) {
|
||||
//LOG(ERROR) << "AE::send immediate";
|
||||
return send_immediate(std::move(message));
|
||||
}
|
||||
//LOG(ERROR) << "AE::send delayed";
|
||||
actor_info_.mailbox().push(std::move(message));
|
||||
pending_signals_.add_signal(ActorSignals::Message);
|
||||
}
|
||||
|
||||
void ActorExecutor::send(ActorSignals signals) {
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (can_send_immediate()) {
|
||||
return send_immediate(signals);
|
||||
}
|
||||
|
||||
pending_signals_.add_signals(signals);
|
||||
}
|
||||
|
||||
void ActorExecutor::start() {
|
||||
//LOG(ERROR) << "START " << actor_info_.get_name() << " " << tag("from_queue", options.from_queue);
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActorSignals signals;
|
||||
SCOPE_EXIT {
|
||||
pending_signals_.add_signals(signals);
|
||||
};
|
||||
|
||||
if (options_.from_queue) {
|
||||
signals.add_signal(ActorSignals::Pop);
|
||||
}
|
||||
|
||||
actor_locker_.try_lock();
|
||||
flags_ = actor_locker_.flags();
|
||||
|
||||
if (!actor_locker_.own_lock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!actor_locker_.can_execute()) {
|
||||
CHECK(!options_.from_queue);
|
||||
return;
|
||||
}
|
||||
|
||||
signals.add_signals(flags().get_signals());
|
||||
if (options_.from_queue) {
|
||||
signals.clear_signal(ActorSignals::Pause);
|
||||
}
|
||||
flags().clear_signals();
|
||||
|
||||
if (flags_.is_closed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
actor_execute_context_.set_actor(&actor_info_.actor());
|
||||
|
||||
while (flush_one_signal(signals)) {
|
||||
if (actor_execute_context_.has_immediate_flags()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (flush_one_message()) {
|
||||
if (actor_execute_context_.has_immediate_flags()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActorExecutor::finish() {
|
||||
//LOG(ERROR) << "FINISH " << actor_info_.get_name() << " " << tag("own_lock", actor_locker_.own_lock());
|
||||
if (!actor_locker_.own_lock()) {
|
||||
if (!pending_signals_.empty() && actor_locker_.add_signals(pending_signals_)) {
|
||||
flags_ = actor_locker_.flags();
|
||||
//LOG(ERROR) << "Own after finish " << actor_info_.get_name() << " " << format::as_binary(flags().raw());
|
||||
} else {
|
||||
//LOG(ERROR) << "DO FINISH " << actor_info_.get_name() << " " << flags();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
flags_.add_signals(pending_signals_);
|
||||
}
|
||||
|
||||
CHECK(actor_locker_.own_lock());
|
||||
|
||||
if (td::unlikely(actor_execute_context_.has_flags())) {
|
||||
flush_context_flags();
|
||||
}
|
||||
|
||||
bool add_to_queue = false;
|
||||
while (true) {
|
||||
// Drop InQueue flag if has pop signal
|
||||
// Can't delay or ignore this signal
|
||||
auto signals = flags().get_signals();
|
||||
if (signals.has_signal(ActorSignals::Pop)) {
|
||||
signals.clear_signal(ActorSignals::Pop);
|
||||
flags().set_signals(signals);
|
||||
flags().set_in_queue(false);
|
||||
//LOG(ERROR) << "clear in_queue " << format::as_binary(flags().raw());
|
||||
}
|
||||
|
||||
//LOG(ERROR) << tag("in_queue", flags().is_in_queue()) << tag("has_signals", flags().has_signals());
|
||||
if (flags_.is_closed()) {
|
||||
// Writing to mailbox and closing actor may happen concurrently
|
||||
// We must ensure that all messages in mailbox will be deleted
|
||||
// Note that an ActorExecute may have to delete messages that was added by itself.
|
||||
actor_info_.mailbox().clear();
|
||||
} else {
|
||||
// No need to add closed actor into queue.
|
||||
if (flags().has_signals() && !flags().is_in_queue()) {
|
||||
add_to_queue = true;
|
||||
flags().set_in_queue(true);
|
||||
}
|
||||
}
|
||||
ActorInfoPtr actor_info_ptr;
|
||||
if (add_to_queue) {
|
||||
actor_info_ptr = actor_info_.actor().get_actor_info_ptr();
|
||||
}
|
||||
if (actor_locker_.try_unlock(flags())) {
|
||||
if (add_to_queue) {
|
||||
dispatcher_.add_to_queue(std::move(actor_info_ptr), flags().get_scheduler_id(), !flags().is_shared());
|
||||
}
|
||||
break;
|
||||
}
|
||||
flags_ = actor_locker_.flags();
|
||||
}
|
||||
//LOG(ERROR) << "DO FINISH " << actor_info_.get_name() << " " << flags();
|
||||
}
|
||||
|
||||
bool ActorExecutor::flush_one_signal(ActorSignals &signals) {
|
||||
auto signal = signals.first_signal();
|
||||
if (!signal) {
|
||||
return false;
|
||||
}
|
||||
switch (signal) {
|
||||
//NB: Signals will be handled in order of their value.
|
||||
// For clarity it conincides with order in this switch
|
||||
case ActorSignals::Pause:
|
||||
actor_execute_context_.set_pause();
|
||||
break;
|
||||
case ActorSignals::Kill:
|
||||
actor_execute_context_.set_stop();
|
||||
break;
|
||||
case ActorSignals::StartUp:
|
||||
actor_info_.actor().start_up();
|
||||
break;
|
||||
case ActorSignals::Wakeup:
|
||||
actor_info_.actor().wake_up();
|
||||
break;
|
||||
case ActorSignals::Alarm:
|
||||
if (actor_execute_context_.get_alarm_timestamp() && actor_execute_context_.get_alarm_timestamp().is_in_past()) {
|
||||
actor_execute_context_.alarm_timestamp() = Timestamp::never();
|
||||
actor_info_.actor().alarm();
|
||||
}
|
||||
break;
|
||||
case ActorSignals::Io:
|
||||
case ActorSignals::Cpu:
|
||||
LOG(FATAL) << "TODO";
|
||||
break;
|
||||
case ActorSignals::Message:
|
||||
pending_signals_.add_signal(ActorSignals::Message);
|
||||
actor_info_.mailbox().pop_all();
|
||||
break;
|
||||
case ActorSignals::Pop:
|
||||
flags().set_in_queue(false);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
signals.clear_signal(signal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ActorExecutor::flush_one_message() {
|
||||
auto message = actor_info_.mailbox().reader().read();
|
||||
//LOG(ERROR) << "flush one message " << !!message << " " << actor_info_.get_name();
|
||||
if (!message) {
|
||||
pending_signals_.clear_signal(ActorSignals::Message);
|
||||
return false;
|
||||
}
|
||||
if (message.is_big() && !options_.from_queue) {
|
||||
actor_info_.mailbox().reader().delay(std::move(message));
|
||||
actor_execute_context_.set_pause();
|
||||
return false;
|
||||
}
|
||||
|
||||
actor_execute_context_.set_link_token(message.get_link_token());
|
||||
message.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ActorExecutor::flush_context_flags() {
|
||||
if (actor_execute_context_.get_stop()) {
|
||||
if (actor_info_.get_alarm_timestamp()) {
|
||||
actor_info_.set_alarm_timestamp(Timestamp::never());
|
||||
dispatcher_.set_alarm_timestamp(actor_info_.actor().get_actor_info_ptr());
|
||||
}
|
||||
flags_.set_closed(true);
|
||||
if (!flags_.get_signals().has_signal(ActorSignals::Signal::StartUp)) {
|
||||
actor_info_.actor().tear_down();
|
||||
}
|
||||
actor_info_.destroy_actor();
|
||||
} else {
|
||||
if (actor_execute_context_.get_pause()) {
|
||||
flags_.add_signals(ActorSignals::one(ActorSignals::Pause));
|
||||
}
|
||||
if (actor_execute_context_.get_yield()) {
|
||||
flags_.add_signals(ActorSignals::one(ActorSignals::Wakeup));
|
||||
}
|
||||
if (actor_execute_context_.get_alarm_flag()) {
|
||||
auto old_timestamp = actor_info_.get_alarm_timestamp();
|
||||
auto new_timestamp = actor_execute_context_.get_alarm_timestamp();
|
||||
if (!(old_timestamp == new_timestamp)) {
|
||||
actor_info_.set_alarm_timestamp(new_timestamp);
|
||||
dispatcher_.set_alarm_timestamp(actor_info_.actor().get_actor_info_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
119
tdactor/td/actor/core/ActorExecutor.h
Normal file
119
tdactor/td/actor/core/ActorExecutor.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorExecuteContext.h"
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
#include "td/actor/core/ActorLocker.h"
|
||||
#include "td/actor/core/ActorMessage.h"
|
||||
#include "td/actor/core/ActorSignals.h"
|
||||
#include "td/actor/core/ActorState.h"
|
||||
#include "td/actor/core/SchedulerContext.h"
|
||||
|
||||
#include "td/utils/format.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorExecutor {
|
||||
public:
|
||||
struct Options {
|
||||
Options &with_from_queue() {
|
||||
from_queue = true;
|
||||
return *this;
|
||||
}
|
||||
Options &with_has_poll(bool new_has_poll) {
|
||||
this->has_poll = new_has_poll;
|
||||
return *this;
|
||||
}
|
||||
bool from_queue{false};
|
||||
bool has_poll{false};
|
||||
};
|
||||
|
||||
ActorExecutor(ActorInfo &actor_info, SchedulerDispatcher &dispatcher, Options options)
|
||||
: actor_info_(actor_info), dispatcher_(dispatcher), options_(options) {
|
||||
old_log_tag_ = LOG_TAG2;
|
||||
LOG_TAG2 = actor_info.get_name().c_str();
|
||||
start();
|
||||
}
|
||||
ActorExecutor(const ActorExecutor &) = delete;
|
||||
ActorExecutor &operator=(const ActorExecutor &) = delete;
|
||||
ActorExecutor(ActorExecutor &&other) = delete;
|
||||
ActorExecutor &operator=(ActorExecutor &&other) = delete;
|
||||
~ActorExecutor() {
|
||||
finish();
|
||||
LOG_TAG2 = old_log_tag_;
|
||||
}
|
||||
|
||||
// our best guess if actor is closed or not
|
||||
bool is_closed() {
|
||||
return flags().is_closed();
|
||||
}
|
||||
|
||||
bool can_send_immediate() {
|
||||
return actor_locker_.own_lock() && !actor_execute_context_.has_immediate_flags() && actor_locker_.can_execute();
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void send_immediate(F &&f, uint64 link_token) {
|
||||
CHECK(can_send_immediate());
|
||||
if (is_closed()) {
|
||||
return;
|
||||
}
|
||||
actor_execute_context_.set_link_token(link_token);
|
||||
f();
|
||||
}
|
||||
|
||||
void send_immediate(ActorMessage message);
|
||||
void send_immediate(ActorSignals signals);
|
||||
void send(ActorMessage message);
|
||||
void send(ActorSignals signals);
|
||||
|
||||
private:
|
||||
ActorInfo &actor_info_;
|
||||
SchedulerDispatcher &dispatcher_;
|
||||
Options options_;
|
||||
ActorLocker actor_locker_{&actor_info_.state(), ActorLocker::Options()
|
||||
.with_can_execute_paused(options_.from_queue)
|
||||
.with_is_shared(!options_.has_poll)
|
||||
.with_scheduler_id(dispatcher_.get_scheduler_id())};
|
||||
|
||||
ActorExecuteContext actor_execute_context_{nullptr, actor_info_.get_alarm_timestamp()};
|
||||
ActorExecuteContext::Guard guard{&actor_execute_context_};
|
||||
|
||||
ActorState::Flags flags_;
|
||||
ActorSignals pending_signals_;
|
||||
|
||||
const char *old_log_tag_;
|
||||
|
||||
ActorState::Flags &flags() {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
void start();
|
||||
void finish();
|
||||
|
||||
bool flush_one(ActorSignals &signals);
|
||||
bool flush_one_signal(ActorSignals &signals);
|
||||
bool flush_one_message();
|
||||
void flush_context_flags();
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
109
tdactor/td/actor/core/ActorInfo.h
Normal file
109
tdactor/td/actor/core/ActorInfo.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorState.h"
|
||||
#include "td/actor/core/ActorMailbox.h"
|
||||
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/Time.h"
|
||||
#include "td/utils/SharedObjectPool.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class Actor;
|
||||
class ActorInfo;
|
||||
using ActorInfoPtr = SharedObjectPool<ActorInfo>::Ptr;
|
||||
class ActorInfo : private HeapNode, private ListNode {
|
||||
public:
|
||||
ActorInfo(std::unique_ptr<Actor> actor, ActorState::Flags state_flags, Slice name)
|
||||
: actor_(std::move(actor)), name_(name.begin(), name.size()) {
|
||||
state_.set_flags_unsafe(state_flags);
|
||||
VLOG(actor) << "Create actor [" << name_ << "]";
|
||||
}
|
||||
~ActorInfo() {
|
||||
VLOG(actor) << "Destroy actor [" << name_ << "]";
|
||||
}
|
||||
|
||||
bool is_alive() const {
|
||||
return !state_.get_flags_unsafe().is_closed();
|
||||
}
|
||||
|
||||
bool has_actor() const {
|
||||
return bool(actor_);
|
||||
}
|
||||
Actor &actor() {
|
||||
CHECK(has_actor());
|
||||
return *actor_;
|
||||
}
|
||||
Actor *actor_ptr() const {
|
||||
return actor_.get();
|
||||
}
|
||||
void destroy_actor() {
|
||||
actor_.reset();
|
||||
}
|
||||
ActorState &state() {
|
||||
return state_;
|
||||
}
|
||||
ActorMailbox &mailbox() {
|
||||
return mailbox_;
|
||||
}
|
||||
CSlice get_name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
HeapNode *as_heap_node() {
|
||||
return this;
|
||||
}
|
||||
static ActorInfo *from_heap_node(HeapNode *node) {
|
||||
return static_cast<ActorInfo *>(node);
|
||||
}
|
||||
|
||||
Timestamp get_alarm_timestamp() const {
|
||||
return Timestamp::at(alarm_timestamp_at_.load(std::memory_order_relaxed));
|
||||
}
|
||||
void set_alarm_timestamp(Timestamp timestamp) {
|
||||
alarm_timestamp_at_.store(timestamp.at(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void pin(ActorInfoPtr ptr) {
|
||||
CHECK(pin_.empty());
|
||||
CHECK(&*ptr == this);
|
||||
pin_ = std::move(ptr);
|
||||
}
|
||||
ActorInfoPtr unpin() {
|
||||
CHECK(!pin_.empty());
|
||||
return std::move(pin_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Actor> actor_;
|
||||
ActorState state_;
|
||||
ActorMailbox mailbox_;
|
||||
std::string name_;
|
||||
std::atomic<double> alarm_timestamp_at_{0};
|
||||
|
||||
ActorInfoPtr pin_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
95
tdactor/td/actor/core/ActorInfoCreator.h
Normal file
95
tdactor/td/actor/core/ActorInfoCreator.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
#include "td/actor/core/Actor.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorInfoCreator {
|
||||
public:
|
||||
class Options {
|
||||
public:
|
||||
Options() = default;
|
||||
|
||||
Options &with_name(Slice new_name) {
|
||||
name = new_name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Options &on_scheduler(SchedulerId new_scheduler_id) {
|
||||
scheduler_id = new_scheduler_id;
|
||||
return *this;
|
||||
}
|
||||
bool has_scheduler() const {
|
||||
return scheduler_id.is_valid();
|
||||
}
|
||||
Options &with_poll(bool has_poll = true) {
|
||||
is_shared = !has_poll;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ActorInfoCreator;
|
||||
Slice name;
|
||||
SchedulerId scheduler_id;
|
||||
bool is_shared{true};
|
||||
bool in_queue{true};
|
||||
//TODO: rename
|
||||
};
|
||||
|
||||
//Create unlocked actor. One must send StartUp signal immediately.
|
||||
ActorInfoPtr create(std::unique_ptr<Actor> actor, const Options &args) {
|
||||
ActorState::Flags flags;
|
||||
flags.set_scheduler_id(args.scheduler_id);
|
||||
if (allow_shared_) {
|
||||
flags.set_shared(args.is_shared);
|
||||
}
|
||||
flags.set_in_queue(args.in_queue);
|
||||
flags.set_signals(ActorSignals::one(ActorSignals::StartUp));
|
||||
|
||||
auto actor_info_ptr = pool_.alloc(std::move(actor), flags, args.name);
|
||||
actor_info_ptr->actor().set_actor_info_ptr(actor_info_ptr);
|
||||
return actor_info_ptr;
|
||||
}
|
||||
|
||||
ActorInfoCreator() = default;
|
||||
explicit ActorInfoCreator(bool allow_shared) : allow_shared_(allow_shared) {
|
||||
}
|
||||
ActorInfoCreator(const ActorInfoCreator &) = delete;
|
||||
ActorInfoCreator &operator=(const ActorInfoCreator &) = delete;
|
||||
ActorInfoCreator(ActorInfoCreator &&other) = delete;
|
||||
ActorInfoCreator &operator=(ActorInfoCreator &&other) = delete;
|
||||
void clear() {
|
||||
pool_.for_each([](auto &actor_info) { actor_info.destroy_actor(); });
|
||||
}
|
||||
~ActorInfoCreator() {
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
SharedObjectPool<ActorInfo> pool_;
|
||||
bool allow_shared_{true};
|
||||
};
|
||||
|
||||
using ActorOptions = ActorInfoCreator::Options;
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
152
tdactor/td/actor/core/ActorLocker.h
Normal file
152
tdactor/td/actor/core/ActorLocker.h
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorSignals.h"
|
||||
#include "td/actor/core/ActorState.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorLocker {
|
||||
public:
|
||||
struct Options {
|
||||
Options() {
|
||||
}
|
||||
bool can_execute_paused = false;
|
||||
bool is_shared = true;
|
||||
SchedulerId scheduler_id;
|
||||
|
||||
Options &with_can_execute_paused(bool new_can_execute_paused) {
|
||||
can_execute_paused = new_can_execute_paused;
|
||||
return *this;
|
||||
}
|
||||
Options &with_is_shared(bool new_is_shared) {
|
||||
is_shared = new_is_shared;
|
||||
return *this;
|
||||
}
|
||||
Options &with_scheduler_id(SchedulerId id) {
|
||||
scheduler_id = id;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
explicit ActorLocker(ActorState *state, Options options = {})
|
||||
: state_(state), flags_(state->get_flags_unsafe()), new_flags_{}, options_{options} {
|
||||
}
|
||||
bool try_lock() {
|
||||
CHECK(!own_lock());
|
||||
while (!can_try_add_signals()) {
|
||||
new_flags_ = flags_;
|
||||
new_flags_.set_locked(true);
|
||||
new_flags_.clear_signals();
|
||||
if (state_->state_.compare_exchange_strong(flags_.raw_ref(), new_flags_.raw(), std::memory_order_acq_rel)) {
|
||||
own_lock_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool try_unlock(ActorState::Flags flags) {
|
||||
CHECK(!flags.is_locked());
|
||||
CHECK(own_lock());
|
||||
// can't unlock with signals set
|
||||
//CHECK(!flags.has_signals());
|
||||
|
||||
flags_ = flags;
|
||||
// try to unlock
|
||||
if (state_->state_.compare_exchange_strong(new_flags_.raw_ref(), flags.raw(), std::memory_order_acq_rel)) {
|
||||
own_lock_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// read all signals
|
||||
flags.set_locked(true);
|
||||
flags.clear_signals();
|
||||
do {
|
||||
flags_.add_signals(new_flags_.get_signals());
|
||||
} while (!state_->state_.compare_exchange_strong(new_flags_.raw_ref(), flags.raw(), std::memory_order_acq_rel));
|
||||
new_flags_ = flags;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool try_add_signals(ActorSignals signals) {
|
||||
CHECK(!own_lock());
|
||||
CHECK(can_try_add_signals());
|
||||
new_flags_ = flags_;
|
||||
new_flags_.add_signals(signals);
|
||||
|
||||
// This is not an optimization.
|
||||
// Sometimes it helps sometimes it makes things worse
|
||||
// It there are a lot of threads concurrently sending signals to an actor it helps
|
||||
// Buf it threre is only one thread, CAS without conficts is much cheaper than full
|
||||
// barrier.
|
||||
if (false && flags_.raw() == new_flags_.raw()) {
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
auto actual_flags = state_->get_flags_unsafe();
|
||||
if (actual_flags.raw() == new_flags_.raw()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return state_->state_.compare_exchange_strong(flags_.raw_ref(), new_flags_.raw(), std::memory_order_acq_rel);
|
||||
}
|
||||
bool add_signals(ActorSignals signals) {
|
||||
CHECK(!own_lock());
|
||||
while (true) {
|
||||
if (can_try_add_signals()) {
|
||||
if (try_add_signals(signals)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (try_lock()) {
|
||||
flags_.add_signals(signals);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool own_lock() const {
|
||||
return own_lock_;
|
||||
}
|
||||
ActorState::Flags flags() const {
|
||||
return flags_;
|
||||
}
|
||||
bool can_execute() const {
|
||||
return flags_.is_shared() == options_.is_shared && flags_.get_scheduler_id() == options_.scheduler_id &&
|
||||
(options_.can_execute_paused || !flags_.get_signals().has_signal(ActorSignals::Pause));
|
||||
}
|
||||
|
||||
private:
|
||||
ActorState *state_{nullptr};
|
||||
ActorState::Flags flags_;
|
||||
ActorState::Flags new_flags_;
|
||||
bool own_lock_{false};
|
||||
Options options_;
|
||||
|
||||
bool can_try_add_signals() const {
|
||||
return flags_.is_locked() || (flags_.is_in_queue() && !can_execute());
|
||||
}
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
68
tdactor/td/actor/core/ActorMailbox.h
Normal file
68
tdactor/td/actor/core/ActorMailbox.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorMessage.h"
|
||||
#include "td/utils/MpscLinkQueue.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorMailbox {
|
||||
public:
|
||||
ActorMailbox() = default;
|
||||
ActorMailbox(const ActorMailbox &) = delete;
|
||||
ActorMailbox &operator=(const ActorMailbox &) = delete;
|
||||
ActorMailbox(ActorMailbox &&other) = delete;
|
||||
ActorMailbox &operator=(ActorMailbox &&other) = delete;
|
||||
~ActorMailbox() {
|
||||
clear();
|
||||
}
|
||||
void push(ActorMessage message) {
|
||||
queue_.push(std::move(message));
|
||||
}
|
||||
void push_unsafe(ActorMessage message) {
|
||||
queue_.push_unsafe(std::move(message));
|
||||
}
|
||||
|
||||
td::MpscLinkQueue<ActorMessage>::Reader &reader() {
|
||||
return reader_;
|
||||
}
|
||||
|
||||
void pop_all() {
|
||||
queue_.pop_all(reader_);
|
||||
}
|
||||
void pop_all_unsafe() {
|
||||
queue_.pop_all_unsafe(reader_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
pop_all();
|
||||
while (reader_.read()) {
|
||||
// skip
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
td::MpscLinkQueue<ActorMessage> queue_;
|
||||
td::MpscLinkQueue<ActorMessage>::Reader reader_;
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
97
tdactor/td/actor/core/ActorMessage.h
Normal file
97
tdactor/td/actor/core/ActorMessage.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorExecuteContext.h"
|
||||
|
||||
#include "td/utils/MpscLinkQueue.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorMessageImpl : private MpscLinkQueueImpl::Node {
|
||||
public:
|
||||
ActorMessageImpl() = default;
|
||||
ActorMessageImpl(const ActorMessageImpl &) = delete;
|
||||
ActorMessageImpl &operator=(const ActorMessageImpl &) = delete;
|
||||
ActorMessageImpl(ActorMessageImpl &&other) = delete;
|
||||
ActorMessageImpl &operator=(ActorMessageImpl &&other) = delete;
|
||||
|
||||
virtual ~ActorMessageImpl() = default;
|
||||
virtual void run() = 0;
|
||||
|
||||
private:
|
||||
friend class ActorMessage;
|
||||
|
||||
// ActorMessage <--> MpscLintQueue::Node
|
||||
// Each actor's mailbox will be a queue
|
||||
static ActorMessageImpl *from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) {
|
||||
return static_cast<ActorMessageImpl *>(node);
|
||||
}
|
||||
MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() {
|
||||
return static_cast<MpscLinkQueueImpl::Node *>(this);
|
||||
}
|
||||
|
||||
uint64 link_token_{EmptyLinkToken};
|
||||
bool is_big_{false};
|
||||
};
|
||||
|
||||
class ActorMessage {
|
||||
public:
|
||||
ActorMessage() = default;
|
||||
explicit ActorMessage(std::unique_ptr<ActorMessageImpl> impl) : impl_(std::move(impl)) {
|
||||
}
|
||||
void run() {
|
||||
CHECK(impl_);
|
||||
impl_->run();
|
||||
}
|
||||
explicit operator bool() {
|
||||
return bool(impl_);
|
||||
}
|
||||
friend class ActorMailbox;
|
||||
|
||||
void set_link_token(uint64 link_token) {
|
||||
impl_->link_token_ = link_token;
|
||||
}
|
||||
uint64 get_link_token() const {
|
||||
return impl_->link_token_;
|
||||
}
|
||||
bool is_big() const {
|
||||
return impl_->is_big_;
|
||||
}
|
||||
void set_big() {
|
||||
impl_->is_big_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ActorMessageImpl> impl_;
|
||||
|
||||
template <class T>
|
||||
friend class td::MpscLinkQueue;
|
||||
|
||||
static ActorMessage from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) {
|
||||
return ActorMessage(std::unique_ptr<ActorMessageImpl>(ActorMessageImpl::from_mpsc_link_queue_node(node)));
|
||||
}
|
||||
MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() {
|
||||
return impl_.release()->to_mpsc_link_queue_node();
|
||||
}
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
118
tdactor/td/actor/core/ActorSignals.h
Normal file
118
tdactor/td/actor/core/ActorSignals.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/bits.h"
|
||||
#include "td/utils/format.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorSignals {
|
||||
public:
|
||||
ActorSignals() = default;
|
||||
uint32 raw() const {
|
||||
return raw_;
|
||||
}
|
||||
static ActorSignals create_raw(uint32 raw) {
|
||||
return ActorSignals(raw);
|
||||
}
|
||||
bool empty() const {
|
||||
return raw_ == 0;
|
||||
}
|
||||
bool has_signal(uint32 signal) const {
|
||||
return (raw_ & (1u << signal)) != 0;
|
||||
}
|
||||
void add_signal(uint32 signal) {
|
||||
raw_ |= (1u << signal);
|
||||
}
|
||||
void add_signals(ActorSignals signals) {
|
||||
raw_ |= signals.raw();
|
||||
}
|
||||
void clear_signal(uint32 signal) {
|
||||
raw_ &= ~(1u << signal);
|
||||
}
|
||||
uint32 first_signal() {
|
||||
if (!raw_) {
|
||||
return 0;
|
||||
}
|
||||
return td::count_trailing_zeroes_non_zero32(raw_);
|
||||
}
|
||||
friend StringBuilder &operator<<(StringBuilder &sb, ActorSignals signals) {
|
||||
sb << "S{";
|
||||
bool was = false;
|
||||
auto add_signal = [&](int signal, auto name) {
|
||||
if (signals.has_signal(signal)) {
|
||||
if (was) {
|
||||
sb << ",";
|
||||
} else {
|
||||
was = true;
|
||||
}
|
||||
sb << name;
|
||||
}
|
||||
};
|
||||
add_signal(Wakeup, "Wakeup");
|
||||
add_signal(Alarm, "Alarm");
|
||||
add_signal(Kill, "Kill");
|
||||
add_signal(Io, "Io");
|
||||
add_signal(Cpu, "Cpu");
|
||||
add_signal(StartUp, "StartUp");
|
||||
add_signal(Pop, "Pop");
|
||||
add_signal(Message, "Message");
|
||||
add_signal(Pause, "Pause");
|
||||
sb << "}";
|
||||
return sb;
|
||||
}
|
||||
enum Signal : uint32 {
|
||||
// Signals in order of priority
|
||||
Pause = 1,
|
||||
Kill = 2, // immediate kill
|
||||
StartUp = 3,
|
||||
Wakeup = 4,
|
||||
Alarm = 5,
|
||||
Io = 6, // move to io thread
|
||||
Cpu = 7, // move to cpu thread
|
||||
// Two signals for mpmc queue logic
|
||||
//
|
||||
// PopSignal is set after actor is popped from queue
|
||||
// When processed it should set InQueue and Pause flags to false.
|
||||
//
|
||||
// MessagesSignal is set after new messages was added to actor
|
||||
// If owner of actor wish to delay message handling, she should set InQueue flag to true and
|
||||
// add actor into mpmc queue.
|
||||
Pop = 8, // got popped from queue
|
||||
Message = 9, // got new message
|
||||
};
|
||||
|
||||
static ActorSignals one(uint32 signal) {
|
||||
ActorSignals res;
|
||||
res.add_signal(signal);
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 raw_{0};
|
||||
friend class ActorState;
|
||||
explicit ActorSignals(uint32 raw) : raw_(raw) {
|
||||
}
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
181
tdactor/td/actor/core/ActorState.h
Normal file
181
tdactor/td/actor/core/ActorState.h
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorSignals.h"
|
||||
#include "td/actor/core/SchedulerId.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/format.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class ActorState {
|
||||
public:
|
||||
class Flags {
|
||||
public:
|
||||
Flags() = default;
|
||||
uint32 raw() const {
|
||||
return raw_;
|
||||
}
|
||||
uint32 &raw_ref() {
|
||||
return raw_;
|
||||
}
|
||||
SchedulerId get_scheduler_id() const {
|
||||
return SchedulerId{static_cast<uint8>(raw_ & SchedulerMask)};
|
||||
}
|
||||
void set_scheduler_id(SchedulerId id) {
|
||||
raw_ = (raw_ & ~SchedulerMask) | id.value();
|
||||
}
|
||||
|
||||
bool is_shared() const {
|
||||
return check_flag(SharedFlag);
|
||||
}
|
||||
void set_shared(bool shared) {
|
||||
set_flag(SharedFlag, shared);
|
||||
}
|
||||
|
||||
bool is_locked() const {
|
||||
return check_flag(LockFlag);
|
||||
}
|
||||
void set_locked(bool locked) {
|
||||
set_flag(LockFlag, locked);
|
||||
}
|
||||
|
||||
bool is_migrate() const {
|
||||
return check_flag(MigrateFlag);
|
||||
}
|
||||
void set_migrate(bool migrate) {
|
||||
set_flag(MigrateFlag, migrate);
|
||||
}
|
||||
|
||||
bool is_closed() const {
|
||||
return check_flag(ClosedFlag);
|
||||
}
|
||||
void set_closed(bool closed) {
|
||||
set_flag(ClosedFlag, closed);
|
||||
}
|
||||
|
||||
bool is_in_queue() const {
|
||||
return check_flag(InQueueFlag);
|
||||
}
|
||||
void set_in_queue(bool in_queue) {
|
||||
set_flag(InQueueFlag, in_queue);
|
||||
}
|
||||
|
||||
bool has_signals() const {
|
||||
return check_flag(SignalMask);
|
||||
}
|
||||
void clear_signals() {
|
||||
set_flag(SignalMask, false);
|
||||
}
|
||||
void set_signals(ActorSignals signals) {
|
||||
raw_ = (raw_ & ~SignalMask) | (signals.raw() << SignalOffset);
|
||||
}
|
||||
void add_signals(ActorSignals signals) {
|
||||
raw_ = raw_ | (signals.raw() << SignalOffset);
|
||||
}
|
||||
ActorSignals get_signals() const {
|
||||
return ActorSignals{(raw_ & SignalMask) >> SignalOffset};
|
||||
}
|
||||
friend StringBuilder &operator<<(StringBuilder &sb, Flags flags) {
|
||||
sb << "ActorFlags{" << flags.get_scheduler_id().value() << ", " << (flags.is_shared() ? "cpu " : "io ")
|
||||
<< (flags.is_migrate() ? "migrate " : "") << (flags.is_closed() ? "closed " : "")
|
||||
<< (flags.is_in_queue() ? "in_queue " : "") << flags.get_signals() << "}";
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 raw_{0};
|
||||
|
||||
friend class ActorState;
|
||||
Flags(uint32 raw) : raw_(raw) {
|
||||
}
|
||||
|
||||
bool check_flag(uint32 mask) const {
|
||||
return (raw_ & mask) != 0;
|
||||
}
|
||||
void set_flag(uint32 mask, bool flag) {
|
||||
raw_ = (raw_ & ~mask) | (flag * mask);
|
||||
}
|
||||
};
|
||||
|
||||
Flags get_flags_unsafe() const {
|
||||
return Flags(state_.load(std::memory_order_relaxed));
|
||||
}
|
||||
void set_flags_unsafe(Flags flags) {
|
||||
state_.store(flags.raw(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ActorLocker;
|
||||
std::atomic<uint32> state_{0};
|
||||
enum : uint32 {
|
||||
SchedulerMask = 255,
|
||||
|
||||
// Actors can be shared or not.
|
||||
// If actor is shared, than any thread may try to lock it
|
||||
// If actor is not shared, than it is owned by its scheduler, and only
|
||||
// its scheduler is allowed to access it
|
||||
// This flag may NOT change during the lifetime of an actor
|
||||
SharedFlag = 1 << 9,
|
||||
|
||||
// Only shared actors need lock
|
||||
// Lock if somebody is going to unlock it eventually.
|
||||
// For example actor is locked, when some scheduler is executing its mailbox
|
||||
// Or it is locked when it is in Mpmc queue, so someone will pop it eventually.
|
||||
LockFlag = 1 << 10,
|
||||
|
||||
// While actor is migrating from one scheduler to another no one is allowed to change it
|
||||
// Could not be set for shared actors.
|
||||
MigrateFlag = 1 << 11,
|
||||
|
||||
// While set all messages are delayed
|
||||
// Dropped from flush_maibox
|
||||
// PauseFlag => InQueueFlag
|
||||
PauseFlag = 1 << 12,
|
||||
|
||||
ClosedFlag = 1 << 13,
|
||||
|
||||
InQueueFlag = 1 << 14,
|
||||
|
||||
// Signals
|
||||
SignalOffset = 15,
|
||||
Signal = 1 << SignalOffset,
|
||||
WakeupSignalFlag = Signal << ActorSignals::Wakeup,
|
||||
AlarmSignalFlag = Signal << ActorSignals::Alarm,
|
||||
KillSignalFlag = Signal << ActorSignals::Kill, // immediate kill
|
||||
IoSignalFlag = Signal << ActorSignals::Io, // move to io thread
|
||||
CpuSignalFlag = Signal << ActorSignals::Cpu, // move to cpu thread
|
||||
StartUpSignalFlag = Signal << ActorSignals::StartUp,
|
||||
MessageSignalFlag = Signal << ActorSignals::Message,
|
||||
PopSignalFlag = Signal << ActorSignals::Pop,
|
||||
PauseSignalFlag = Signal << ActorSignals::Pause,
|
||||
|
||||
SignalMask = WakeupSignalFlag | AlarmSignalFlag | KillSignalFlag | IoSignalFlag | CpuSignalFlag |
|
||||
StartUpSignalFlag | MessageSignalFlag | PopSignalFlag | PauseSignalFlag
|
||||
};
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
59
tdactor/td/actor/core/Context.h
Normal file
59
tdactor/td/actor/core/Context.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/port/thread_local.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
template <class Impl>
|
||||
class Context {
|
||||
public:
|
||||
static Impl *get() {
|
||||
return context_;
|
||||
}
|
||||
class Guard {
|
||||
public:
|
||||
explicit Guard(Impl *new_context) {
|
||||
old_context_ = context_;
|
||||
context_ = new_context;
|
||||
}
|
||||
~Guard() {
|
||||
context_ = old_context_;
|
||||
}
|
||||
Guard(const Guard &) = delete;
|
||||
Guard &operator=(const Guard &) = delete;
|
||||
Guard(Guard &&) = delete;
|
||||
Guard &operator=(Guard &&) = delete;
|
||||
|
||||
private:
|
||||
Impl *old_context_;
|
||||
};
|
||||
|
||||
private:
|
||||
static TD_THREAD_LOCAL Impl *context_;
|
||||
};
|
||||
|
||||
template <class Impl>
|
||||
TD_THREAD_LOCAL Impl *Context<Impl>::context_;
|
||||
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
47
tdactor/td/actor/core/CpuWorker.cpp
Normal file
47
tdactor/td/actor/core/CpuWorker.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/core/CpuWorker.h"
|
||||
|
||||
#include "td/actor/core/ActorExecutor.h"
|
||||
#include "td/actor/core/SchedulerContext.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
void CpuWorker::run() {
|
||||
auto thread_id = get_thread_id();
|
||||
auto &dispatcher = *SchedulerContext::get();
|
||||
|
||||
int yields = 0;
|
||||
while (true) {
|
||||
SchedulerMessage message;
|
||||
if (queue_.try_pop(message, thread_id)) {
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue());
|
||||
yields = waiter_.stop_wait(yields, thread_id);
|
||||
} else {
|
||||
yields = waiter_.wait(yields, thread_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
41
tdactor/td/actor/core/CpuWorker.h
Normal file
41
tdactor/td/actor/core/CpuWorker.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/SchedulerMessage.h"
|
||||
|
||||
#include "td/utils/MpmcQueue.h"
|
||||
#include "td/utils/MpmcWaiter.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class CpuWorker {
|
||||
public:
|
||||
CpuWorker(MpmcQueue<SchedulerMessage> &queue, MpmcWaiter &waiter) : queue_(queue), waiter_(waiter) {
|
||||
}
|
||||
void run();
|
||||
|
||||
private:
|
||||
MpmcQueue<SchedulerMessage> &queue_;
|
||||
MpmcWaiter &waiter_;
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
100
tdactor/td/actor/core/IoWorker.cpp
Normal file
100
tdactor/td/actor/core/IoWorker.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/core/IoWorker.h"
|
||||
|
||||
#include "td/actor/core/ActorExecutor.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
void IoWorker::start_up() {
|
||||
#if TD_PORT_POSIX
|
||||
auto &poll = SchedulerContext::get()->get_poll();
|
||||
poll.subscribe(queue_.reader_get_event_fd().get_poll_info().extract_pollable_fd(nullptr), PollFlags::Read());
|
||||
#endif
|
||||
}
|
||||
void IoWorker::tear_down() {
|
||||
#if TD_PORT_POSIX
|
||||
auto &poll = SchedulerContext::get()->get_poll();
|
||||
poll.unsubscribe(queue_.reader_get_event_fd().get_poll_info().get_pollable_fd_ref());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IoWorker::run_once(double timeout) {
|
||||
auto &dispatcher = *SchedulerContext::get();
|
||||
#if TD_PORT_POSIX
|
||||
auto &poll = SchedulerContext::get()->get_poll();
|
||||
#endif
|
||||
auto &heap = SchedulerContext::get()->get_heap();
|
||||
|
||||
auto now = Time::now(); // update Time::now_cached()
|
||||
while (!heap.empty() && heap.top_key() <= now) {
|
||||
auto *heap_node = heap.pop();
|
||||
auto *actor_info = ActorInfo::from_heap_node(heap_node);
|
||||
|
||||
auto id = actor_info->unpin();
|
||||
ActorExecutor executor(*actor_info, dispatcher, ActorExecutor::Options().with_has_poll(true));
|
||||
if (executor.can_send_immediate()) {
|
||||
executor.send_immediate(ActorSignals::one(ActorSignals::Alarm));
|
||||
} else {
|
||||
executor.send(ActorSignals::one(ActorSignals::Alarm));
|
||||
}
|
||||
}
|
||||
|
||||
const int size = queue_.reader_wait_nonblock();
|
||||
for (int i = 0; i < size; i++) {
|
||||
auto message = queue_.reader_get_unsafe();
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
if (message->state().get_flags_unsafe().is_shared()) {
|
||||
// should check actors timeout
|
||||
dispatcher.set_alarm_timestamp(message);
|
||||
continue;
|
||||
}
|
||||
ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue().with_has_poll(true));
|
||||
}
|
||||
queue_.reader_flush();
|
||||
|
||||
bool can_sleep = size == 0 && timeout != 0;
|
||||
int32 timeout_ms = 0;
|
||||
if (can_sleep) {
|
||||
auto wakeup_timestamp = Timestamp::in(timeout);
|
||||
if (!heap.empty()) {
|
||||
wakeup_timestamp.relax(Timestamp::at(heap.top_key()));
|
||||
}
|
||||
timeout_ms = static_cast<int>(wakeup_timestamp.in() * 1000) + 1;
|
||||
if (timeout_ms < 0) {
|
||||
timeout_ms = 0;
|
||||
}
|
||||
//const int thirty_seconds = 30 * 1000;
|
||||
//if (timeout_ms > thirty_seconds) {
|
||||
//timeout_ms = thirty_seconds;
|
||||
//}
|
||||
}
|
||||
#if TD_PORT_POSIX
|
||||
poll.run(timeout_ms);
|
||||
#elif TD_PORT_WINDOWS
|
||||
queue_.reader_get_event_fd().wait(timeout_ms);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
43
tdactor/td/actor/core/IoWorker.h
Normal file
43
tdactor/td/actor/core/IoWorker.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/SchedulerContext.h"
|
||||
#include "td/actor/core/SchedulerMessage.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class IoWorker {
|
||||
public:
|
||||
explicit IoWorker(MpscPollableQueue<SchedulerMessage> &queue) : queue_(queue) {
|
||||
}
|
||||
|
||||
void start_up();
|
||||
void tear_down();
|
||||
|
||||
bool run_once(double timeout);
|
||||
|
||||
private:
|
||||
MpscPollableQueue<SchedulerMessage> &queue_;
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
288
tdactor/td/actor/core/Scheduler.cpp
Normal file
288
tdactor/td/actor/core/Scheduler.cpp
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/core/Scheduler.h"
|
||||
|
||||
#include "td/actor/core/CpuWorker.h"
|
||||
#include "td/actor/core/IoWorker.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
Scheduler::Scheduler(std::shared_ptr<SchedulerGroupInfo> scheduler_group_info, SchedulerId id, size_t cpu_threads_count)
|
||||
: scheduler_group_info_(std::move(scheduler_group_info)), cpu_threads_(cpu_threads_count) {
|
||||
scheduler_group_info_->active_scheduler_count++;
|
||||
info_ = &scheduler_group_info_->schedulers.at(id.value());
|
||||
info_->id = id;
|
||||
if (cpu_threads_count != 0) {
|
||||
info_->cpu_threads_count = cpu_threads_count;
|
||||
info_->cpu_queue = std::make_unique<MpmcQueue<SchedulerMessage>>(1024, max_thread_count());
|
||||
info_->cpu_queue_waiter = std::make_unique<MpmcWaiter>();
|
||||
}
|
||||
info_->io_queue = std::make_unique<MpscPollableQueue<SchedulerMessage>>();
|
||||
info_->io_queue->init();
|
||||
|
||||
info_->cpu_workers.resize(cpu_threads_count);
|
||||
for (auto &worker : info_->cpu_workers) {
|
||||
worker = std::make_unique<WorkerInfo>(WorkerInfo::Type::Cpu, true);
|
||||
}
|
||||
info_->io_worker = std::make_unique<WorkerInfo>(WorkerInfo::Type::Io, !info_->cpu_workers.empty());
|
||||
|
||||
poll_.init();
|
||||
io_worker_ = std::make_unique<IoWorker>(*info_->io_queue);
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
if (info_->id.value() == 0) {
|
||||
scheduler_group_info_->iocp.init();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
// should stop
|
||||
stop();
|
||||
do_stop();
|
||||
}
|
||||
|
||||
void Scheduler::start() {
|
||||
for (size_t i = 0; i < cpu_threads_.size(); i++) {
|
||||
cpu_threads_[i] = td::thread([this, i] {
|
||||
this->run_in_context_impl(*this->info_->cpu_workers[i],
|
||||
[this] { CpuWorker(*info_->cpu_queue, *info_->cpu_queue_waiter).run(); });
|
||||
});
|
||||
cpu_threads_[i].set_name(PSLICE() << "#" << info_->id.value() << ":cpu#" << i);
|
||||
}
|
||||
#if TD_PORT_WINDOWS
|
||||
// FIXME: use scheduler_id
|
||||
if (info_->id.value() == 0) {
|
||||
scheduler_group_info_->iocp_thread = td::thread([this] {
|
||||
WorkerInfo info;
|
||||
info.type = WorkerInfo::Type::Cpu;
|
||||
this->run_in_context_impl(info, [this] { scheduler_group_info_->iocp.loop(); });
|
||||
});
|
||||
}
|
||||
#endif
|
||||
this->run_in_context([this] { this->io_worker_->start_up(); });
|
||||
}
|
||||
|
||||
bool Scheduler::run(double timeout) {
|
||||
bool res;
|
||||
run_in_context_impl(*info_->io_worker, [this, timeout, &res] {
|
||||
if (SchedulerContext::get()->is_stop_requested()) {
|
||||
res = false;
|
||||
} else {
|
||||
res = io_worker_->run_once(timeout);
|
||||
}
|
||||
if (!res) {
|
||||
if (!is_stopped_) {
|
||||
io_worker_->tear_down();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!res) {
|
||||
do_stop();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void Scheduler::do_stop() {
|
||||
if (is_stopped_) {
|
||||
return;
|
||||
}
|
||||
// wait other threads to finish
|
||||
for (auto &thread : cpu_threads_) {
|
||||
thread.join();
|
||||
}
|
||||
// Can't do anything else, other schedulers may send queries to this one.
|
||||
// Must wait till every scheduler is stopped first..
|
||||
is_stopped_ = true;
|
||||
|
||||
io_worker_.reset();
|
||||
poll_.clear();
|
||||
heap_.for_each([](auto &key, auto &node) { ActorInfo::from_heap_node(node)->unpin(); });
|
||||
|
||||
std::unique_lock<std::mutex> lock(scheduler_group_info_->active_scheduler_count_mutex);
|
||||
scheduler_group_info_->active_scheduler_count--;
|
||||
scheduler_group_info_->active_scheduler_count_condition_variable.notify_all();
|
||||
}
|
||||
|
||||
Scheduler::ContextImpl::ContextImpl(ActorInfoCreator *creator, SchedulerId scheduler_id,
|
||||
SchedulerGroupInfo *scheduler_group, Poll *poll, KHeap<double> *heap)
|
||||
: creator_(creator), scheduler_id_(scheduler_id), scheduler_group_(scheduler_group), poll_(poll), heap_(heap) {
|
||||
}
|
||||
|
||||
SchedulerId Scheduler::ContextImpl::get_scheduler_id() const {
|
||||
return scheduler_id_;
|
||||
}
|
||||
void Scheduler::ContextImpl::add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) {
|
||||
if (!scheduler_id.is_valid()) {
|
||||
scheduler_id = get_scheduler_id();
|
||||
}
|
||||
auto &info = scheduler_group()->schedulers.at(scheduler_id.value());
|
||||
if (need_poll || !info.cpu_queue) {
|
||||
info.io_queue->writer_put(std::move(actor_info_ptr));
|
||||
} else {
|
||||
info.cpu_queue->push(std::move(actor_info_ptr), get_thread_id());
|
||||
info.cpu_queue_waiter->notify();
|
||||
}
|
||||
}
|
||||
|
||||
ActorInfoCreator &Scheduler::ContextImpl::get_actor_info_creator() {
|
||||
return *creator_;
|
||||
}
|
||||
|
||||
bool Scheduler::ContextImpl::has_poll() {
|
||||
return poll_ != nullptr;
|
||||
}
|
||||
Poll &Scheduler::ContextImpl::get_poll() {
|
||||
CHECK(has_poll());
|
||||
return *poll_;
|
||||
}
|
||||
|
||||
bool Scheduler::ContextImpl::has_heap() {
|
||||
return heap_ != nullptr;
|
||||
}
|
||||
KHeap<double> &Scheduler::ContextImpl::get_heap() {
|
||||
CHECK(has_heap());
|
||||
return *heap_;
|
||||
}
|
||||
|
||||
void Scheduler::ContextImpl::set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) {
|
||||
// Ideas for optimization
|
||||
// 1. Several cpu actors with separate heaps. They ask io worker to update timeout only when it has been changed
|
||||
// 2. Update timeout only when it has increased
|
||||
// 3. Use signal-like logic to combile multiple timeout updates into one
|
||||
if (!has_heap()) {
|
||||
add_to_queue(actor_info_ptr, {}, true);
|
||||
return;
|
||||
}
|
||||
// we are in PollWorker
|
||||
CHECK(has_heap());
|
||||
auto &heap = get_heap();
|
||||
auto *heap_node = actor_info_ptr->as_heap_node();
|
||||
auto timestamp = actor_info_ptr->get_alarm_timestamp();
|
||||
if (timestamp) {
|
||||
if (heap_node->in_heap()) {
|
||||
heap.fix(timestamp.at(), heap_node);
|
||||
} else {
|
||||
actor_info_ptr->pin(actor_info_ptr);
|
||||
heap.insert(timestamp.at(), heap_node);
|
||||
}
|
||||
} else {
|
||||
if (heap_node->in_heap()) {
|
||||
actor_info_ptr->unpin();
|
||||
heap.erase(heap_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Scheduler::ContextImpl::is_stop_requested() {
|
||||
return scheduler_group()->is_stop_requested;
|
||||
}
|
||||
|
||||
void Scheduler::ContextImpl::stop() {
|
||||
bool expect_false = false;
|
||||
// Trying to set close_flag_ to true with CAS
|
||||
auto &group = *scheduler_group();
|
||||
if (!group.is_stop_requested.compare_exchange_strong(expect_false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify all workers of all schedulers
|
||||
for (auto &scheduler_info : group.schedulers) {
|
||||
scheduler_info.io_queue->writer_put({});
|
||||
for (size_t i = 0; i < scheduler_info.cpu_threads_count; i++) {
|
||||
scheduler_info.cpu_queue->push({}, get_thread_id());
|
||||
scheduler_info.cpu_queue_waiter->notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
void Scheduler::close_scheduler_group(SchedulerGroupInfo &group_info) {
|
||||
//LOG(DEBUG) << "close scheduler group";
|
||||
// Cannot close scheduler group before somebody asked to stop them
|
||||
CHECK(group_info.is_stop_requested);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(group_info.active_scheduler_count_mutex);
|
||||
group_info.active_scheduler_count_condition_variable.wait(lock,
|
||||
[&] { return group_info.active_scheduler_count == 0; });
|
||||
}
|
||||
|
||||
//FIXME
|
||||
//ContextImpl context(&group_info.schedulers[0].io_worker->actor_info_creator, SchedulerId{0}, &group_info, nullptr,
|
||||
// nullptr);
|
||||
//SchedulerContext::Guard guard(&context);
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
detail::Iocp::Guard iocp_guard(&group_info.iocp);
|
||||
group_info.iocp.interrupt_loop();
|
||||
group_info.iocp_thread.join();
|
||||
#endif
|
||||
|
||||
// Drain all queues
|
||||
int it = 0;
|
||||
for (bool queues_are_empty = false; !queues_are_empty;) {
|
||||
queues_are_empty = true;
|
||||
for (auto &scheduler_info : group_info.schedulers) {
|
||||
// Drain io queue
|
||||
auto &io_queue = *scheduler_info.io_queue;
|
||||
while (true) {
|
||||
int n = io_queue.reader_wait_nonblock();
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
while (n-- > 0) {
|
||||
auto message = io_queue.reader_get_unsafe();
|
||||
// message's destructor is called
|
||||
queues_are_empty = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Drain cpu queue
|
||||
if (scheduler_info.cpu_queue) {
|
||||
auto &cpu_queue = *scheduler_info.cpu_queue;
|
||||
while (true) {
|
||||
SchedulerMessage message;
|
||||
if (!cpu_queue.try_pop(message, get_thread_id())) {
|
||||
break;
|
||||
}
|
||||
// message's destructor is called
|
||||
queues_are_empty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (++it > 100) {
|
||||
LOG(FATAL) << "Failed to drain all queues";
|
||||
}
|
||||
}
|
||||
LOG_IF(ERROR, it > 2) << "It took more than one iteration to drain queues";
|
||||
|
||||
// Just to destroy all elements should be ok.
|
||||
for (auto &scheduler_info : group_info.schedulers) {
|
||||
scheduler_info.io_queue.reset();
|
||||
scheduler_info.cpu_queue.reset();
|
||||
|
||||
// Do not destroy worker infos. run_in_context will crash if they are empty
|
||||
scheduler_info.io_worker->actor_info_creator.clear();
|
||||
for (auto &worker : scheduler_info.cpu_workers) {
|
||||
worker->actor_info_creator.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
223
tdactor/td/actor/core/Scheduler.h
Normal file
223
tdactor/td/actor/core/Scheduler.h
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorExecuteContext.h"
|
||||
#include "td/actor/core/ActorExecutor.h"
|
||||
#include "td/actor/core/Actor.h"
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
#include "td/actor/core/ActorInfoCreator.h"
|
||||
#include "td/actor/core/ActorLocker.h"
|
||||
#include "td/actor/core/ActorMailbox.h"
|
||||
#include "td/actor/core/ActorMessage.h"
|
||||
#include "td/actor/core/Context.h"
|
||||
#include "td/actor/core/SchedulerContext.h"
|
||||
#include "td/actor/core/SchedulerId.h"
|
||||
#include "td/actor/core/SchedulerMessage.h"
|
||||
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/MpmcQueue.h"
|
||||
#include "td/utils/MpmcWaiter.h"
|
||||
#include "td/utils/MpscLinkQueue.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/port/Poll.h"
|
||||
#include "td/utils/port/detail/Iocp.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Time.h"
|
||||
#include "td/utils/type_traits.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class IoWorker;
|
||||
|
||||
struct WorkerInfo {
|
||||
enum class Type { Io, Cpu } type{Type::Io};
|
||||
WorkerInfo() = default;
|
||||
explicit WorkerInfo(Type type, bool allow_shared) : type(type), actor_info_creator(allow_shared) {
|
||||
}
|
||||
ActorInfoCreator actor_info_creator;
|
||||
};
|
||||
|
||||
struct SchedulerInfo {
|
||||
SchedulerId id;
|
||||
// will be read by all workers is any thread
|
||||
std::unique_ptr<MpmcQueue<SchedulerMessage>> cpu_queue;
|
||||
std::unique_ptr<MpmcWaiter> cpu_queue_waiter;
|
||||
// only scheduler itself may read from io_queue_
|
||||
std::unique_ptr<MpscPollableQueue<SchedulerMessage>> io_queue;
|
||||
size_t cpu_threads_count{0};
|
||||
|
||||
std::unique_ptr<WorkerInfo> io_worker;
|
||||
std::vector<std::unique_ptr<WorkerInfo>> cpu_workers;
|
||||
};
|
||||
|
||||
struct SchedulerGroupInfo {
|
||||
explicit SchedulerGroupInfo(size_t n) : schedulers(n) {
|
||||
}
|
||||
std::atomic<bool> is_stop_requested{false};
|
||||
|
||||
int active_scheduler_count{0};
|
||||
std::mutex active_scheduler_count_mutex;
|
||||
std::condition_variable active_scheduler_count_condition_variable;
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
td::detail::Iocp iocp;
|
||||
td::thread iocp_thread;
|
||||
#endif
|
||||
std::vector<SchedulerInfo> schedulers;
|
||||
};
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
static constexpr int32 max_thread_count() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
static int32 get_thread_id() {
|
||||
auto thread_id = ::td::get_thread_id();
|
||||
CHECK(thread_id < max_thread_count());
|
||||
return thread_id;
|
||||
}
|
||||
|
||||
Scheduler(std::shared_ptr<SchedulerGroupInfo> scheduler_group_info, SchedulerId id, size_t cpu_threads_count);
|
||||
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
Scheduler &operator=(const Scheduler &) = delete;
|
||||
Scheduler(Scheduler &&other) = delete;
|
||||
Scheduler &operator=(Scheduler &&other) = delete;
|
||||
~Scheduler();
|
||||
|
||||
void start();
|
||||
|
||||
template <class F>
|
||||
void run_in_context(F &&f) {
|
||||
run_in_context_impl(*info_->io_worker, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void run_in_context_external(F &&f) {
|
||||
WorkerInfo info;
|
||||
info.type = WorkerInfo::Type::Cpu;
|
||||
run_in_context_impl(*info_->io_worker, std::forward<F>(f));
|
||||
}
|
||||
|
||||
bool run(double timeout);
|
||||
|
||||
// Just syntactic sugar
|
||||
void stop() {
|
||||
run_in_context([] { SchedulerContext::get()->stop(); });
|
||||
}
|
||||
|
||||
SchedulerId get_scheduler_id() const {
|
||||
return info_->id;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SchedulerGroupInfo> scheduler_group_info_;
|
||||
SchedulerInfo *info_;
|
||||
std::vector<td::thread> cpu_threads_;
|
||||
bool is_stopped_{false};
|
||||
Poll poll_;
|
||||
KHeap<double> heap_;
|
||||
std::unique_ptr<IoWorker> io_worker_;
|
||||
|
||||
class ContextImpl : public SchedulerContext {
|
||||
public:
|
||||
ContextImpl(ActorInfoCreator *creator, SchedulerId scheduler_id, SchedulerGroupInfo *scheduler_group, Poll *poll,
|
||||
KHeap<double> *heap);
|
||||
|
||||
SchedulerId get_scheduler_id() const override;
|
||||
void add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) override;
|
||||
ActorInfoCreator &get_actor_info_creator() override;
|
||||
|
||||
bool has_poll() override;
|
||||
Poll &get_poll() override;
|
||||
|
||||
bool has_heap() override;
|
||||
KHeap<double> &get_heap() override;
|
||||
|
||||
void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) override;
|
||||
|
||||
bool is_stop_requested() override;
|
||||
void stop() override;
|
||||
|
||||
private:
|
||||
SchedulerGroupInfo *scheduler_group() const {
|
||||
return scheduler_group_;
|
||||
}
|
||||
|
||||
ActorInfoCreator *creator_;
|
||||
SchedulerId scheduler_id_;
|
||||
SchedulerGroupInfo *scheduler_group_;
|
||||
Poll *poll_;
|
||||
|
||||
KHeap<double> *heap_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
void run_in_context_impl(WorkerInfo &worker_info, F &&f) {
|
||||
#if TD_PORT_WINDOWS
|
||||
td::detail::Iocp::Guard iocp_guard(&scheduler_group_info_->iocp);
|
||||
#endif
|
||||
bool is_io_worker = worker_info.type == WorkerInfo::Type::Io;
|
||||
ContextImpl context(&worker_info.actor_info_creator, info_->id, scheduler_group_info_.get(),
|
||||
is_io_worker ? &poll_ : nullptr, is_io_worker ? &heap_ : nullptr);
|
||||
SchedulerContext::Guard guard(&context);
|
||||
f();
|
||||
}
|
||||
|
||||
void do_stop();
|
||||
|
||||
public:
|
||||
static void close_scheduler_group(SchedulerGroupInfo &group_info);
|
||||
};
|
||||
|
||||
// Actor messages
|
||||
class ActorMessageHangup : public core::ActorMessageImpl {
|
||||
public:
|
||||
void run() override {
|
||||
ActorExecuteContext::get()->actor().hangup();
|
||||
}
|
||||
};
|
||||
class ActorMessageHangupShared : public core::ActorMessageImpl {
|
||||
public:
|
||||
void run() override {
|
||||
ActorExecuteContext::get()->actor().hangup_shared();
|
||||
}
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
61
tdactor/td/actor/core/SchedulerContext.h
Normal file
61
tdactor/td/actor/core/SchedulerContext.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/core/Context.h"
|
||||
#include "td/actor/core/SchedulerId.h"
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
#include "td/actor/core/ActorInfoCreator.h"
|
||||
|
||||
#include "td/utils/port/Poll.h"
|
||||
#include "td/utils/Heap.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class SchedulerDispatcher {
|
||||
public:
|
||||
virtual ~SchedulerDispatcher() = default;
|
||||
|
||||
virtual SchedulerId get_scheduler_id() const = 0;
|
||||
virtual void add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) = 0;
|
||||
virtual void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) = 0;
|
||||
};
|
||||
|
||||
class SchedulerContext : public Context<SchedulerContext>, public SchedulerDispatcher {
|
||||
public:
|
||||
virtual ~SchedulerContext() = default;
|
||||
|
||||
// ActorCreator Interface
|
||||
virtual ActorInfoCreator &get_actor_info_creator() = 0;
|
||||
|
||||
// Poll interface
|
||||
virtual bool has_poll() = 0;
|
||||
virtual Poll &get_poll() = 0;
|
||||
|
||||
// Timeout interface
|
||||
virtual bool has_heap() = 0;
|
||||
virtual KHeap<double> &get_heap() = 0;
|
||||
|
||||
// Stop all schedulers
|
||||
virtual bool is_stop_requested() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
49
tdactor/td/actor/core/SchedulerId.h
Normal file
49
tdactor/td/actor/core/SchedulerId.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
class SchedulerId {
|
||||
public:
|
||||
SchedulerId() : id_(-1) {
|
||||
}
|
||||
explicit SchedulerId(uint8 id) : id_(id) {
|
||||
}
|
||||
bool is_valid() const {
|
||||
return id_ >= 0;
|
||||
}
|
||||
uint8 value() const {
|
||||
CHECK(is_valid());
|
||||
return static_cast<uint8>(id_);
|
||||
}
|
||||
bool operator==(SchedulerId scheduler_id) const {
|
||||
return id_ == scheduler_id.id_;
|
||||
}
|
||||
|
||||
private:
|
||||
int32 id_{0};
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
29
tdactor/td/actor/core/SchedulerMessage.h
Normal file
29
tdactor/td/actor/core/SchedulerMessage.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/core/ActorInfo.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
namespace core {
|
||||
using SchedulerMessage = ActorInfoPtr;
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
} // namespace td
|
1099
tdactor/test/actors_core.cpp
Normal file
1099
tdactor/test/actors_core.cpp
Normal file
File diff suppressed because it is too large
Load diff
498
tdactor/test/actors_promise.cpp
Normal file
498
tdactor/test/actors_promise.cpp
Normal file
|
@ -0,0 +1,498 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
#include "td/actor/MultiPromise.h"
|
||||
#include "td/utils/MovableValue.h"
|
||||
#include "td/utils/tests.h"
|
||||
|
||||
template <class T>
|
||||
class X {
|
||||
public:
|
||||
X() = default;
|
||||
X(X &&) = default;
|
||||
template <class S>
|
||||
X(S s) : t(s) {
|
||||
}
|
||||
T t;
|
||||
};
|
||||
|
||||
TEST(Actor, promise) {
|
||||
using Int = td::MovableValue<int>;
|
||||
using td::Promise;
|
||||
using td::Result;
|
||||
|
||||
auto set_int = [](td::Result<Int> &destination) {
|
||||
return [&destination](Int value) { destination = std::move(value); };
|
||||
};
|
||||
auto set_result_int = [](Result<Int> &destination) {
|
||||
return [&destination](Result<Int> value) { destination = std::move(value); };
|
||||
};
|
||||
|
||||
{
|
||||
Result<Int> result{2};
|
||||
{
|
||||
Promise<Int> promise = set_int(result);
|
||||
promise.set_value(Int{3});
|
||||
}
|
||||
ASSERT_TRUE(result.is_ok());
|
||||
ASSERT_EQ(result.ok().get(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
Result<Int> result{2};
|
||||
{
|
||||
Promise<Int> promise = set_int(result);
|
||||
(void)promise;
|
||||
// will set Int{} on destruction
|
||||
}
|
||||
ASSERT_TRUE(result.is_ok());
|
||||
ASSERT_EQ(result.ok().get(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Result<Int> result{2};
|
||||
{
|
||||
Promise<Int> promise = set_result_int(result);
|
||||
promise.set_value(Int{3});
|
||||
}
|
||||
ASSERT_TRUE(result.is_ok());
|
||||
ASSERT_EQ(result.ok().get(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
Result<Int> result{2};
|
||||
{
|
||||
Promise<Int> promise = set_result_int(result);
|
||||
(void)promise;
|
||||
// will set Status::Error() on destruction
|
||||
}
|
||||
ASSERT_TRUE(result.is_error());
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<int> res;
|
||||
Promise<td::Unit> x = [a = std::make_unique<int>(5), &res](td::Unit) mutable { res = std::move(a); };
|
||||
x(td::Unit());
|
||||
CHECK(*res == 5);
|
||||
}
|
||||
|
||||
{//{
|
||||
//Promise<Int> promise;
|
||||
//std::tuple<Promise<Int> &&> f(std::move(promise));
|
||||
//std::tuple<Promise<Int>> x = std::move(f);
|
||||
//}
|
||||
|
||||
{
|
||||
//using T = Result<int>;
|
||||
//using T = std::unique_ptr<int>;
|
||||
//using T = std::function<int()>;
|
||||
//using T = std::vector<int>;
|
||||
//using T = X<int>;
|
||||
////using T = Promise<Int>;
|
||||
//T f;
|
||||
//std::tuple<T &&> g(std::move(f));
|
||||
//std::tuple<T> h = std::move(g);
|
||||
}}
|
||||
|
||||
{
|
||||
int result = 0;
|
||||
auto promise = td::lambda_promise<int>([&](auto x) { result = x.move_as_ok(); });
|
||||
promise.set_value(5);
|
||||
ASSERT_EQ(5, result);
|
||||
|
||||
Promise<int> promise2 = [&](auto x) { result = x.move_as_ok(); };
|
||||
promise2.set_value(6);
|
||||
ASSERT_EQ(6, result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Actor, safe_promise) {
|
||||
int res = 0;
|
||||
{
|
||||
td::Promise<int> promise = td::PromiseCreator::lambda([&](int x) { res = x; });
|
||||
auto safe_promise = td::SafePromise<int>(std::move(promise), 2);
|
||||
promise = std::move(safe_promise);
|
||||
ASSERT_EQ(res, 0);
|
||||
auto safe_promise2 = td::SafePromise<int>(std::move(promise), 3);
|
||||
}
|
||||
ASSERT_EQ(res, 3);
|
||||
}
|
||||
|
||||
TEST(Actor2, actor_lost_promise) {
|
||||
using namespace td::actor;
|
||||
using namespace td;
|
||||
Scheduler scheduler({1}, Scheduler::Paused);
|
||||
|
||||
auto watcher = td::create_shared_destructor([] {
|
||||
LOG(ERROR) << "STOP";
|
||||
SchedulerContext::get()->stop();
|
||||
});
|
||||
scheduler.run_in_context([watcher = std::move(watcher)] {
|
||||
class B : public Actor {
|
||||
public:
|
||||
void start_up() override {
|
||||
stop();
|
||||
}
|
||||
uint32 query(uint32 x) {
|
||||
return x * x;
|
||||
}
|
||||
};
|
||||
class A : public Actor {
|
||||
public:
|
||||
A(std::shared_ptr<td::Destructor> watcher) : watcher_(std::move(watcher)) {
|
||||
}
|
||||
void start_up() {
|
||||
b_ = create_actor<B>(ActorOptions().with_name("B"));
|
||||
//send_closure(b_, &B::query, 2, [self = actor_id(this)](uint32 y) { send_closure(self, &A::on_result, 2, y); });
|
||||
send_closure_later(b_, &B::query, 2,
|
||||
[self = actor_id(this), a = std::make_unique<int>()](Result<uint32> y) mutable {
|
||||
LOG(ERROR) << "!";
|
||||
CHECK(y.is_error());
|
||||
send_closure(self, &A::finish);
|
||||
});
|
||||
send_closure(b_, &B::query, 2, [self = actor_id(this), a = std::make_unique<int>()](Result<uint32> y) mutable {
|
||||
LOG(ERROR) << "!";
|
||||
CHECK(y.is_error());
|
||||
send_closure(self, &A::finish);
|
||||
});
|
||||
}
|
||||
void finish() {
|
||||
LOG(ERROR) << "FINISH";
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<td::Destructor> watcher_;
|
||||
td::actor::ActorOwn<B> b_;
|
||||
};
|
||||
create_actor<A>(ActorOptions().with_name("A").with_poll(), watcher).release();
|
||||
});
|
||||
scheduler.run();
|
||||
}
|
||||
|
||||
TEST(Actor2, MultiPromise) {
|
||||
using namespace td;
|
||||
MultiPromise::Options fail_on_error;
|
||||
fail_on_error.ignore_errors = false;
|
||||
MultiPromise::Options ignore_errors;
|
||||
ignore_errors.ignore_errors = true;
|
||||
|
||||
std::string str;
|
||||
auto log = [&](Result<Unit> res) {
|
||||
if (res.is_ok()) {
|
||||
str += "OK;";
|
||||
} else {
|
||||
str += PSTRING() << "E" << res.error().code() << ";";
|
||||
}
|
||||
};
|
||||
auto clear = [&] { str = ""; };
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(ignore_errors);
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.add_promise(log);
|
||||
ASSERT_EQ("", str);
|
||||
}
|
||||
ASSERT_EQ("OK;", str);
|
||||
}
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(ignore_errors);
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.add_promise(log);
|
||||
mp_init.get_promise().set_error(Status::Error(1));
|
||||
ASSERT_EQ("", str);
|
||||
}
|
||||
ASSERT_EQ("OK;", str);
|
||||
}
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(ignore_errors);
|
||||
Promise<> promise;
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.add_promise(log);
|
||||
promise = mp_init.get_promise();
|
||||
}
|
||||
ASSERT_EQ("", str);
|
||||
{
|
||||
auto mp_init = mp.add_promise_or_init(log);
|
||||
ASSERT_TRUE(!mp_init);
|
||||
}
|
||||
promise.set_error(Status::Error(2));
|
||||
ASSERT_EQ("OK;OK;", str);
|
||||
clear();
|
||||
{
|
||||
auto mp_init = mp.add_promise_or_init(log);
|
||||
ASSERT_TRUE(mp_init);
|
||||
ASSERT_EQ("", str);
|
||||
}
|
||||
ASSERT_EQ("OK;", str);
|
||||
}
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(fail_on_error);
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.get_promise().set_value(Unit());
|
||||
mp_init.add_promise(log);
|
||||
ASSERT_EQ("", str);
|
||||
}
|
||||
ASSERT_EQ("OK;", str);
|
||||
}
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(fail_on_error);
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.get_promise().set_value(Unit());
|
||||
mp_init.add_promise(log);
|
||||
mp_init.get_promise().set_error(Status::Error(1));
|
||||
ASSERT_EQ("E1;", str);
|
||||
clear();
|
||||
mp_init.get_promise().set_error(Status::Error(2));
|
||||
ASSERT_EQ("", str);
|
||||
mp_init.add_promise(log);
|
||||
ASSERT_EQ("E1;", str);
|
||||
}
|
||||
ASSERT_EQ("E1;", str);
|
||||
}
|
||||
|
||||
{
|
||||
clear();
|
||||
MultiPromise mp(fail_on_error);
|
||||
Promise<> promise;
|
||||
{
|
||||
auto mp_init = mp.init_guard();
|
||||
mp_init.get_promise().set_value(Unit());
|
||||
mp_init.add_promise(log);
|
||||
promise = mp_init.get_promise();
|
||||
}
|
||||
ASSERT_EQ("", str);
|
||||
{
|
||||
auto mp_init = mp.add_promise_or_init(log);
|
||||
ASSERT_TRUE(mp_init.empty());
|
||||
}
|
||||
promise.set_error(Status::Error(2));
|
||||
ASSERT_EQ("E2;E2;", str);
|
||||
clear();
|
||||
|
||||
{
|
||||
auto mp_init = mp.add_promise_or_init(log);
|
||||
ASSERT_TRUE(!mp_init.empty());
|
||||
}
|
||||
ASSERT_EQ("OK;", str);
|
||||
}
|
||||
}
|
||||
|
||||
#if TD_HAVE_COROUTINES
|
||||
#include <experimental/coroutine>
|
||||
namespace td {
|
||||
template <class T = Unit>
|
||||
struct task {
|
||||
struct final_awaiter {
|
||||
bool await_ready() const noexcept {
|
||||
return false;
|
||||
}
|
||||
template <class P>
|
||||
std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<P> continuation) noexcept {
|
||||
return continuation.promise().continuation_;
|
||||
}
|
||||
void await_resume() noexcept {
|
||||
}
|
||||
};
|
||||
struct promise_type {
|
||||
task get_return_object() {
|
||||
return task{*this};
|
||||
}
|
||||
std::experimental::suspend_always initial_suspend() {
|
||||
return {};
|
||||
}
|
||||
final_awaiter final_suspend() {
|
||||
return final_awaiter{};
|
||||
}
|
||||
void return_value(T v) {
|
||||
value_ = v;
|
||||
}
|
||||
T move_value() {
|
||||
return std::move(value_.value());
|
||||
}
|
||||
void unhandled_exception() {
|
||||
}
|
||||
|
||||
optional<T> value_;
|
||||
std::experimental::coroutine_handle<> continuation_;
|
||||
};
|
||||
|
||||
// awaiter
|
||||
std::experimental::coroutine_handle<promise_type> coroutine_handle_;
|
||||
task(task &&other) = default;
|
||||
task(promise_type &promise)
|
||||
: coroutine_handle_(std::experimental::coroutine_handle<promise_type>::from_promise(promise)) {
|
||||
}
|
||||
|
||||
bool await_ready() const noexcept {
|
||||
return !coroutine_handle_ || coroutine_handle_.done();
|
||||
}
|
||||
std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<> continuation) noexcept {
|
||||
coroutine_handle_.promise().continuation_ = continuation;
|
||||
return coroutine_handle_;
|
||||
}
|
||||
T await_resume() noexcept {
|
||||
return coroutine_handle_.promise().move_value();
|
||||
}
|
||||
};
|
||||
|
||||
task<int> f() {
|
||||
co_return 1;
|
||||
}
|
||||
task<int> g() {
|
||||
co_return 2;
|
||||
}
|
||||
task<int> h() {
|
||||
auto a = co_await f();
|
||||
auto b = co_await g();
|
||||
co_return a + b;
|
||||
}
|
||||
|
||||
struct immediate_task {
|
||||
struct promise_type {
|
||||
immediate_task get_return_object() {
|
||||
return {};
|
||||
}
|
||||
std::experimental::suspend_never initial_suspend() {
|
||||
return {};
|
||||
}
|
||||
std::experimental::suspend_never final_suspend() {
|
||||
return {};
|
||||
}
|
||||
void return_void() {
|
||||
}
|
||||
void unhandled_exception() {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct OnActor {
|
||||
public:
|
||||
template <class T>
|
||||
OnActor(T &&actor_id) : actor_id_(actor_id.as_actor_ref()) {
|
||||
}
|
||||
bool await_ready() const noexcept {
|
||||
return false;
|
||||
}
|
||||
void await_suspend(std::experimental::coroutine_handle<> continuation) noexcept {
|
||||
//TODO: destroy if lambda is lost
|
||||
send_lambda(actor_id_, [continuation]() mutable { continuation.resume(); });
|
||||
}
|
||||
void await_resume() noexcept {
|
||||
}
|
||||
|
||||
private:
|
||||
actor::detail::ActorRef actor_id_;
|
||||
};
|
||||
|
||||
immediate_task check_h() {
|
||||
LOG(ERROR) << "check_h: call h";
|
||||
auto c = co_await h();
|
||||
LOG(ERROR) << "check_h: after call h";
|
||||
ASSERT_EQ(3, c);
|
||||
}
|
||||
|
||||
TEST(ActorCoro, Task) {
|
||||
check_h();
|
||||
}
|
||||
namespace actor {
|
||||
class AsyncQuery {};
|
||||
|
||||
class Printer : public Actor {
|
||||
public:
|
||||
void f();
|
||||
void print_a() {
|
||||
LOG(ERROR) << "a";
|
||||
}
|
||||
void print_b() {
|
||||
LOG(ERROR) << "b";
|
||||
}
|
||||
};
|
||||
|
||||
class SampleActor : public Actor {
|
||||
public:
|
||||
SampleActor(std::shared_ptr<td::Destructor> watcher) : watcher_(std::move(watcher)) {
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Destructor> watcher_;
|
||||
ActorOwn<Printer> printer_;
|
||||
void start_up() override {
|
||||
printer_ = create_actor<Printer>("Printer");
|
||||
run_coroutine();
|
||||
}
|
||||
task<Unit> print_a() {
|
||||
auto self = actor_id(this);
|
||||
LOG(ERROR) << "enter print_a";
|
||||
co_await OnActor(printer_);
|
||||
detail::current_actor<Printer>().print_a();
|
||||
co_await OnActor(self);
|
||||
LOG(ERROR) << "exit print_a";
|
||||
co_return{};
|
||||
}
|
||||
task<Unit> print_b() {
|
||||
auto self = actor_id(this);
|
||||
LOG(ERROR) << "enter print_b";
|
||||
co_await OnActor(printer_);
|
||||
detail::current_actor<Printer>().print_b();
|
||||
co_await OnActor(self);
|
||||
LOG(ERROR) << "exit print_b";
|
||||
co_return{};
|
||||
}
|
||||
|
||||
immediate_task run_coroutine() {
|
||||
co_await print_a();
|
||||
co_await print_b();
|
||||
stop();
|
||||
}
|
||||
};
|
||||
} // namespace actor
|
||||
|
||||
TEST(ActorCoro, Simple) {
|
||||
using namespace td::actor;
|
||||
using namespace td;
|
||||
Scheduler scheduler({1});
|
||||
|
||||
auto watcher = td::create_shared_destructor([] {
|
||||
LOG(ERROR) << "STOP";
|
||||
SchedulerContext::get()->stop();
|
||||
});
|
||||
scheduler.run_in_context([watcher = std::move(watcher)] {
|
||||
create_actor<actor::SampleActor>(ActorOptions().with_name("SampleActor").with_poll(), watcher).release();
|
||||
});
|
||||
scheduler.run();
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue