// Copyright (c) 2020, Tencent Inc.
// All rights reserved.
#ifndef TRPC_FIBERS_TIMED_MUTEX_H
#define TRPC_FIBERS_TIMED_MUTEX_H
#include <chrono> // std::chrono
#include <atomic> // std::atomic
#include <cstddef>
#include <string>
#include "trpc/log/trpc_log.h" // TRPC_ASSERT
#include "trpc/util/likely.h"
#include "trpc/util/chrono/chrono.h"
#include "trpc/coroutine/fiber_mutex.h"
#include "trpc/runtime/threadmodel/fiber/detail/waitable.h" // WaitBlock Waitable 等待队列实现
namespace trpc::fiber::detail {
class FiberTimedMutex {
// typedef std::chrono::steady_clock steady_clock_t;
private:
trpc::fiber::detail::Waitable wait_queue_; // 等待队列
trpc::fiber::detail::FiberEntity* owner_{nullptr}; // 锁
trpc::Spinlock splk_; // 自旋锁
bool try_lock_until_( std::chrono::steady_clock::time_point const& timeout_time) noexcept;
public:
FiberTimedMutex() = default;
~FiberTimedMutex() {
TRPC_ASSERT( nullptr == owner_); // 析构的时候不能有fiber持有锁。为true时无异常。否则异常
// TRPC_ASSERT( wait_queue_.Empty()); // wait_queue_析构时会判断等待队列是否为空
}
FiberTimedMutex( FiberTimedMutex const&) = delete;
FiberTimedMutex & operator=( FiberTimedMutex const&) = delete;
void lock();
bool try_lock();
template< typename Clock, typename Duration >
bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time_) {
std::chrono::steady_clock::time_point timeout_time = std::chrono::steady_clock::now() + ( timeout_time_ - Clock::now());
return try_lock_until_( timeout_time);
}
template< typename Rep, typename Period >
bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration) {
return try_lock_until_( std::chrono::steady_clock::now() + timeout_duration);
}
void unlock();
};
class fiber_error : public std::system_error {
public:
explicit fiber_error( std::error_code ec) :
std::system_error{ ec } {
}
fiber_error( std::error_code ec, const char * what_arg) :
std::system_error{ ec, what_arg } {
}
fiber_error( std::error_code ec, std::string const& what_arg) :
std::system_error{ ec, what_arg } {
}
~fiber_error() override = default;
};
class lock_error : public fiber_error {
public:
explicit lock_error( std::error_code ec) :
fiber_error{ ec } {
}
lock_error( std::error_code ec, const char * what_arg) :
fiber_error{ ec, what_arg } {
}
lock_error( std::error_code ec, std::string const& what_arg) :
fiber_error{ ec, what_arg } {
}
};
}
#endif // TRPC_FIBERS_TIMED_MUTEX_H
// Copyright (c) 2020, Tencent Inc.
// All rights reserved.
#include "trpc/coroutine/fiber_timed_mutex.h"
#include <system_error> // std::make_error_code
#include <iostream>
#include "trpc/coroutine/fiber_condition_variable.h"
#include "trpc/runtime/threadmodel/fiber/detail/fiber_entity.h"
#include "trpc/runtime/threadmodel/fiber/detail/scheduling_group.h"
#include "trpc/util/delayed_init.h"
#include "trpc/util/ref_ptr.h"
// using trpc::fiber::detail::AsyncWaker;
namespace trpc::fiber::detail {
class AsyncWaker {
public:
AsyncWaker(SchedulingGroup* sg, FiberEntity* self, WaitBlock* wb)
: sg_(sg), self_(self), wb_(wb) {}
~AsyncWaker() { TRPC_CHECK_EQ(timer_, std::uint64_t(0), "Have you called `Cleanup()`?"); }
void SetTimer(std::chrono::steady_clock::time_point expires_at) {
wait_cb_ = MakeRefCounted<WaitCb>();
wait_cb_->waiter = self_;
// Create a callback function
auto timer_cb = [wait_cb = wait_cb_ /* ref-counted */, wb = wb_](auto) {
std::scoped_lock lk(wait_cb->lock);
if (!wait_cb->awake) {
if (wb->satisfied.exchange(true, std::memory_order_relaxed)) {
return;
}
wait_cb->waiter->scheduling_group->Resume( // 异步回调函数会恢复因此被挂起的fiber
wait_cb->waiter, std::unique_lock(wait_cb->waiter->scheduler_lock));
}
};
timer_ = sg_->CreateTimer(expires_at, timer_cb);
sg_->EnableTimer(timer_);
}
void Cleanup() {
sg_->RemoveTimer(std::exchange(timer_, 0));
{
std::scoped_lock _(wait_cb_->lock);
wait_cb_->awake = true;
}
}
private:
struct WaitCb : RefCounted<WaitCb> {
Spinlock lock;
FiberEntity* waiter;
bool awake = false;
};
SchedulingGroup* sg_;
FiberEntity* self_;
WaitBlock* wb_;
RefPtr<WaitCb> wait_cb_;
std::uint64_t timer_ = 0;
};
bool // The current fiber attempts to acquire the fiber_timed_lock, blocking until the specified time to acquire ownership. If the specified time has elapsed, the behavior is the same as try_lock().
FiberTimedMutex::try_lock_until_( std::chrono::steady_clock::time_point const& timeout_time) noexcept
{
for (int i=0;i<1000;i++) // keep trying to lock
{
FiberEntity* current = GetCurrentFiberEntity(); // get the current fiber context
std::unique_lock slk(current->scheduler_lock); // we need to hold `scheduler_lock` until we finished context swap.
if ( nullptr == owner_)
{ // if equql to nullptr, locked
owner_ = current;
return true;
}
SchedulingGroup* sg = current->scheduling_group;
WaitBlock wb = {.waiter = current};
DelayedInit<AsyncWaker> awaker;
if (i == 0)
{
// Add to the waiting queue.
TRPC_CHECK(wait_queue_.AddWaiter(&wb));
// Set asynchronous timed tasks.
if (timeout_time!= std::chrono::steady_clock::time_point::max())
{
awaker.Init(sg, current, &wb);
awaker->SetTimer(timeout_time);
}
}
// If the lock is not obtained, suspend the current fiber and restore other fibers
sg->Suspend(current, std::move(slk));
// Determine whether to time out after fiber recovery
if (std::chrono::steady_clock::now() < timeout_time)
{
// wait_queue_.TryRemoveWaiter(&wb); // prevent duplicate insertion
if (awaker)
awaker->Cleanup();
continue;
}
// Has timed out, removed from the waiting queue
wait_queue_.TryRemoveWaiter(&wb);
if (awaker)
{
awaker->Cleanup();
}
return false;
}
}
void // The current fiber blocks until ownership is acquired. Returns true if the current fiber takes ownership, false otherwise.
FiberTimedMutex::lock()
{
while ( true)
{
auto current = GetCurrentFiberEntity();
// Prohibit the fiber that has been locked, and lock it again to cause deadlock
if ( TRPC_UNLIKELY( current == owner_) )
{
throw lock_error{
std::make_error_code( std::errc::resource_deadlock_would_occur),
"trpc fiber: a deadlock is detected"
};
}
std::unique_lock slk(current->scheduler_lock);
if ( nullptr == owner_)
{
owner_ = current;
return;
}
WaitBlock wb = {.waiter = current};
TRPC_CHECK(wait_queue_.AddWaiter(&wb));
// Suspend the current fiber and resume other fiber
current->scheduling_group->Suspend(current, std::move(slk));
}
}
bool // Try to lock, return true if successful, false if failed.
FiberTimedMutex::try_lock()
{
FiberEntity* current = GetCurrentFiberEntity();
std::unique_lock slk(splk_);
if ( TRPC_UNLIKELY( current == owner_) )
{
throw lock_error{
std::make_error_code( std::errc::resource_deadlock_would_occur),
"trpc fiber: a deadlock is detected" };
}
if ( nullptr == owner_)
owner_ = current;
slk.unlock();
// let other fiber release the lock
current->scheduling_group->Yield(current); // Can not get the lock, call other fibers immediately
return current == owner_;
}
void // Release the ownership of fiber_timed_mutex.
FiberTimedMutex::unlock()
{
FiberEntity* current = GetCurrentFiberEntity();
std::unique_lock slk(splk_);
if ( TRPC_UNLIKELY( current != owner_) )
{ // Who locks, who unlocks
throw lock_error{
std::make_error_code( std::errc::operation_not_permitted),
"TRPC fiber: no privilege to perform the operation"
};
}
owner_ = nullptr;
// Notify a fiber waiting for a lock
auto fiber = wait_queue_.WakeOne();
if (!fiber)
return;
fiber->scheduling_group->Resume(fiber, std::unique_lock(fiber->scheduler_lock));
}
}
#include "trpc/coroutine/fiber_timed_mutex.h"
#include <chrono>
#include <iostream>
#include <stdexcept>
#include <time.h>
#include "gtest/gtest.h" // 谷歌命令解析工具
#include "trpc/coroutine/fiber.h"
#include "trpc/coroutine/testing/fiber_runtime.h"
#include "trpc/util/random.h"
using namespace std;
typedef std::chrono::nanoseconds ns; // 纳秒
typedef std::chrono::milliseconds ms; // 毫秒 s , ms, us, ns
namespace trpc::fiber::detail {
Fiber::Attributes attr{.launch_policy = fiber::Launch::Dispatch};
// 测试fiber的调用顺序
TEST(FiberTimedMutexTest, test0) {
RunAsFiber([&] {
// std::cout << "\nget lock back" << std::endl;
Fiber f(attr,[]{
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
// std::cout << "prapare get the lock" << std::endl;
FiberSleepFor(250 * 1ms);
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
ns d = t1 - t0 - ms(250);
// std::cout << d.count() << "ms" << std::endl;
});
// std::cout << "come back" << std::endl;
f.Join(); // 阻塞等待f完成
});
}
// 测试lock()
TEST(FiberTimedMutexTest, test1) {
RunAsFiber([&] {
FiberTimedMutex m;
m.lock(); // 外层fiber先上锁
Fiber f(attr,[&m]{
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
m.lock(); // 内层fiber再上锁
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
m.unlock();
ns d = t1 - t0 - ms(250);
// std::cout << d.count() << "ns" << std::endl;
TRPC_CHECK(d > ns(0)); // within 2.5 ms
});
FiberSleepFor(250 * 1ms);
m.unlock();
f.Join(); // 阻塞等待f完成
});
}
// 测试try_lock()
TEST(FiberTimedMutexTest, test2) {
RunAsFiber([&] {
FiberTimedMutex m;
m.lock(); // 外层fiber先上锁
Fiber f(attr,[&m]{
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
while ( ! m.try_lock() ); // 必须返回false
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
m.unlock();
ns d = t1 - t0 - ms(250);
// std::cout << d.count() << "ns" << std::endl;
TRPC_CHECK(d > ns(0)); // within 2.5 ms
});
FiberSleepFor(300 * 1ms);
m.unlock();
f.Join(); // 阻塞等待f完成
});
}
// 测试try_lock_for
TEST(FiberTimedMutexTest, test3) {
RunAsFiber([] {
FiberTimedMutex m;
Fiber f(attr,[&m]{
m.lock();
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
TRPC_CHECK ( !m.try_lock_for(ms(1000))); // 必须返回false
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
m.unlock();
});
f.Join();
});
}
// 测试try_lock_until
TEST(FiberTimedMutexTest, test4) {
RunAsFiber([] {
FiberTimedMutex m;
Fiber f(attr,[&m]{
m.lock();
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
TRPC_CHECK(m.try_lock_until(std::chrono::steady_clock::now() + ms(1000)) == false);
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
m.unlock();
ns d = t1 - t0 - ms(250);
TRPC_CHECK(d > ns(0)); // within 5ms
});
f.Join();
});
}
// 测试try_lock_until
TEST(FiberTimedMutexTest, test5) {
RunAsFiber([] {
FiberTimedMutex m;
m.lock();
Fiber f(attr,[&m]{
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
TRPC_CHECK( m.try_lock_until(std::chrono::steady_clock::now() + ms(250)) == true );
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
m.unlock();
ns d = t1 - t0;
TRPC_CHECK(ns(0) < d); // within 5ms
});
FiberSleepFor(100 * 1ms);
m.unlock();
f.Join();
});
}
// 测试多个fiber同时请求锁,模拟多个fiber同事请求同一个锁
TEST(FiberTimedMutexTest, test6) {
RunAsFiber([] {
FiberTimedMutex m;
auto func = [&m]{
if (m.try_lock_until(std::chrono::steady_clock::now() + ms(4)) == true )// 每多等1ms,就会多一个fiber获取锁
{
std::cout << "get it!\n";
FiberSleepFor(1 * 1ms);
m.unlock();
}
else
{
std::cout << "timeout!\n";
};
};
std::vector<Fiber> v;
for(int i=0;i<9;i++){
v.push_back(Fiber(Fiber::Attributes{.launch_policy = fiber::Launch::Post},func));
}
std::vector<Fiber>::iterator it;
for(it=v.begin();it!=v.end();it++)
{
it->Join();
}
});
}
} // namespace trpc
// ldconfig -p | grep test
// g++ -std=c++11 test5.cpp -lboost_fiber -lboost_context -lboost_unit_test_framework -o test5
记录从0到1的过程:
【腾讯文档】限流
https://docs.qq.com/doc/DQ3VNRHpPZnR3QUZK