2018-10-17 14:12:10 +02:00
|
|
|
// Matrix Construct
|
|
|
|
//
|
|
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice is present in all copies. The
|
|
|
|
// full license for this software is available in the LICENSE file.
|
|
|
|
|
|
|
|
#include <ircd/asio.h>
|
|
|
|
|
|
|
|
/// Record of the ID of the thread static initialization took place on.
|
|
|
|
decltype(ircd::ios::static_thread_id)
|
|
|
|
ircd::ios::static_thread_id
|
|
|
|
{
|
|
|
|
std::this_thread::get_id()
|
|
|
|
};
|
|
|
|
|
|
|
|
/// "main" thread for IRCd; the one the main context landed on.
|
|
|
|
decltype(ircd::ios::main_thread_id)
|
|
|
|
ircd::ios::main_thread_id;
|
|
|
|
|
|
|
|
decltype(ircd::ios::user)
|
|
|
|
ircd::ios::user;
|
|
|
|
|
2019-06-01 01:06:55 +02:00
|
|
|
decltype(ircd::boost_version_api)
|
|
|
|
ircd::boost_version_api
|
|
|
|
{
|
|
|
|
"boost", info::versions::API, BOOST_VERSION,
|
|
|
|
{
|
|
|
|
BOOST_VERSION / 100000,
|
|
|
|
BOOST_VERSION / 100 % 1000,
|
|
|
|
BOOST_VERSION % 100,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
decltype(ircd::boost_version_abi)
|
|
|
|
ircd::boost_version_abi
|
|
|
|
{
|
2019-06-02 23:21:40 +02:00
|
|
|
"boost", info::versions::ABI //TODO: get this
|
2019-06-01 01:06:55 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// init
|
|
|
|
//
|
|
|
|
|
2018-10-17 14:12:10 +02:00
|
|
|
void
|
|
|
|
ircd::ios::init(asio::io_context &user)
|
|
|
|
{
|
|
|
|
// Sample the ID of this thread. Since this is the first transfer of
|
|
|
|
// control to libircd after static initialization we have nothing to
|
|
|
|
// consider a main thread yet. We need something set for many assertions
|
|
|
|
// to pass until ircd::main() is entered which will reset this to where
|
|
|
|
// ios.run() is really running.
|
|
|
|
main_thread_id = std::this_thread::get_id();
|
|
|
|
|
|
|
|
// Set a reference to the user's ios_service
|
|
|
|
ios::user = &user;
|
|
|
|
}
|
|
|
|
|
2019-03-27 00:53:31 +01:00
|
|
|
//
|
|
|
|
// descriptor
|
|
|
|
//
|
|
|
|
|
2019-04-17 05:48:00 +02:00
|
|
|
template<>
|
|
|
|
decltype(ircd::util::instance_list<ircd::ios::descriptor>::allocator)
|
|
|
|
ircd::util::instance_list<ircd::ios::descriptor>::allocator
|
|
|
|
{};
|
|
|
|
|
2019-03-27 00:53:31 +01:00
|
|
|
template<>
|
|
|
|
decltype(ircd::util::instance_list<ircd::ios::descriptor>::list)
|
|
|
|
ircd::util::instance_list<ircd::ios::descriptor>::list
|
2019-04-17 05:48:00 +02:00
|
|
|
{
|
|
|
|
allocator
|
|
|
|
};
|
2019-03-27 00:53:31 +01:00
|
|
|
|
|
|
|
decltype(ircd::ios::descriptor::ids)
|
|
|
|
ircd::ios::descriptor::ids;
|
|
|
|
|
|
|
|
//
|
|
|
|
// descriptor::descriptor
|
|
|
|
//
|
|
|
|
|
2019-03-27 10:49:28 +01:00
|
|
|
ircd::ios::descriptor::descriptor(const string_view &name,
|
|
|
|
const decltype(allocator) &allocator,
|
2019-03-29 00:56:55 +01:00
|
|
|
const decltype(deallocator) &deallocator,
|
|
|
|
const bool &continuation)
|
2019-03-27 00:53:31 +01:00
|
|
|
:name{name}
|
2019-03-29 01:09:46 +01:00
|
|
|
,stats{std::make_unique<struct stats>()}
|
2019-03-27 10:49:28 +01:00
|
|
|
,allocator{allocator}
|
|
|
|
,deallocator{deallocator}
|
2019-03-29 00:56:55 +01:00
|
|
|
,continuation{continuation}
|
2019-03-27 00:53:31 +01:00
|
|
|
{
|
2019-03-27 10:49:28 +01:00
|
|
|
assert(allocator);
|
|
|
|
assert(deallocator);
|
2019-03-27 00:53:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::descriptor::~descriptor()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 10:49:28 +01:00
|
|
|
void
|
|
|
|
ircd::ios::descriptor::default_deallocator(handler &handler,
|
|
|
|
void *const &ptr,
|
|
|
|
const size_t &size)
|
2019-09-11 00:11:25 +02:00
|
|
|
noexcept
|
2019-03-27 10:49:28 +01:00
|
|
|
{
|
2019-06-23 08:28:48 +02:00
|
|
|
#ifdef __clang__
|
|
|
|
::operator delete(ptr);
|
|
|
|
#else
|
2019-03-27 10:49:28 +01:00
|
|
|
::operator delete(ptr, size);
|
2019-06-23 08:28:48 +02:00
|
|
|
#endif
|
2019-03-27 10:49:28 +01:00
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 10:49:28 +01:00
|
|
|
void *
|
|
|
|
ircd::ios::descriptor::default_allocator(handler &handler,
|
|
|
|
const size_t &size)
|
|
|
|
{
|
|
|
|
return ::operator new(size);
|
|
|
|
}
|
|
|
|
|
2019-03-29 01:09:46 +01:00
|
|
|
//
|
|
|
|
// descriptor::stats
|
|
|
|
//
|
|
|
|
|
|
|
|
ircd::ios::descriptor::stats::stats()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::descriptor::stats::~stats()
|
|
|
|
noexcept
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-03-29 01:16:47 +01:00
|
|
|
struct ircd::ios::descriptor::stats &
|
2019-03-29 01:09:46 +01:00
|
|
|
ircd::ios::descriptor::stats::operator+=(const stats &o)
|
|
|
|
&
|
|
|
|
{
|
2019-04-11 07:52:33 +02:00
|
|
|
queued += o.queued;
|
2019-03-29 01:09:46 +01:00
|
|
|
calls += o.calls;
|
|
|
|
faults += o.faults;
|
|
|
|
allocs += o.allocs;
|
|
|
|
alloc_bytes += o.alloc_bytes;
|
|
|
|
frees += o.frees;
|
|
|
|
free_bytes += o.free_bytes;
|
|
|
|
slice_total += o.slice_total;
|
|
|
|
slice_last += o.slice_last;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2019-03-27 00:53:31 +01:00
|
|
|
//
|
|
|
|
// handler
|
|
|
|
//
|
|
|
|
|
2019-07-16 01:02:40 +02:00
|
|
|
decltype(ircd::ios::handler::current)
|
|
|
|
thread_local
|
2019-03-27 10:06:55 +01:00
|
|
|
ircd::ios::handler::current;
|
|
|
|
|
2019-07-16 01:02:40 +02:00
|
|
|
decltype(ircd::ios::handler::epoch)
|
|
|
|
thread_local
|
|
|
|
ircd::ios::handler::epoch;
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::cold]]
|
2019-03-27 00:53:31 +01:00
|
|
|
bool
|
|
|
|
ircd::ios::handler::fault(handler *const &handler)
|
2019-07-16 01:01:35 +02:00
|
|
|
noexcept
|
2019-03-27 00:53:31 +01:00
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
2019-03-27 06:03:48 +01:00
|
|
|
|
2019-03-29 01:09:46 +01:00
|
|
|
assert(descriptor.stats);
|
|
|
|
auto &stats(*descriptor.stats);
|
|
|
|
++stats.faults;
|
|
|
|
|
|
|
|
bool ret(false);
|
2019-03-27 06:03:48 +01:00
|
|
|
// leave() isn't called if we return false so the tsc counter
|
|
|
|
// needs to be tied off here instead.
|
|
|
|
if(!ret)
|
|
|
|
{
|
2019-04-02 20:09:37 +02:00
|
|
|
stats.slice_last = cycles() - handler->slice_start;
|
2019-03-29 01:09:46 +01:00
|
|
|
stats.slice_total += stats.slice_last;
|
2019-03-30 21:11:55 +01:00
|
|
|
|
|
|
|
assert(handler::current == handler);
|
|
|
|
handler::current = nullptr;
|
2019-03-27 06:03:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2019-03-27 00:53:31 +01:00
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 00:53:31 +01:00
|
|
|
void
|
|
|
|
ircd::ios::handler::leave(handler *const &handler)
|
2019-07-16 01:01:35 +02:00
|
|
|
noexcept
|
2019-03-27 00:53:31 +01:00
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
2019-03-29 01:09:46 +01:00
|
|
|
|
|
|
|
assert(descriptor.stats);
|
|
|
|
auto &stats(*descriptor.stats);
|
2019-04-02 20:09:37 +02:00
|
|
|
stats.slice_last = cycles() - handler->slice_start;
|
2019-03-29 01:09:46 +01:00
|
|
|
stats.slice_total += stats.slice_last;
|
|
|
|
|
2019-03-27 10:06:55 +01:00
|
|
|
assert(handler::current == handler);
|
|
|
|
handler::current = nullptr;
|
2019-03-27 00:53:31 +01:00
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 00:53:31 +01:00
|
|
|
void
|
|
|
|
ircd::ios::handler::enter(handler *const &handler)
|
2019-07-16 01:01:35 +02:00
|
|
|
noexcept
|
2019-03-27 00:53:31 +01:00
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
2019-03-29 01:09:46 +01:00
|
|
|
|
|
|
|
assert(descriptor.stats);
|
|
|
|
auto &stats(*descriptor.stats);
|
|
|
|
++stats.calls;
|
2019-07-16 01:02:40 +02:00
|
|
|
++handler::epoch;
|
2019-03-27 10:06:55 +01:00
|
|
|
assert(!handler::current);
|
|
|
|
handler::current = handler;
|
2019-04-02 20:09:37 +02:00
|
|
|
handler->slice_start = cycles();
|
2019-03-27 00:53:31 +01:00
|
|
|
}
|
2019-03-27 05:22:22 +01:00
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-29 00:56:55 +01:00
|
|
|
bool
|
|
|
|
ircd::ios::handler::continuation(handler *const &handler)
|
2019-07-16 01:01:35 +02:00
|
|
|
noexcept
|
2019-03-29 00:56:55 +01:00
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
|
|
|
return descriptor.continuation;
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 05:22:22 +01:00
|
|
|
void
|
|
|
|
ircd::ios::handler::deallocate(handler *const &handler,
|
|
|
|
void *const &ptr,
|
|
|
|
const size_t &size)
|
2019-07-16 01:01:35 +02:00
|
|
|
noexcept
|
2019-03-27 05:22:22 +01:00
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
2019-03-29 01:09:46 +01:00
|
|
|
|
2019-03-27 10:49:28 +01:00
|
|
|
descriptor.deallocator(*handler, ptr, size);
|
2019-03-29 01:09:46 +01:00
|
|
|
|
|
|
|
assert(descriptor.stats);
|
|
|
|
auto &stats(*descriptor.stats);
|
|
|
|
stats.free_bytes += size;
|
|
|
|
++stats.frees;
|
2019-03-27 05:22:22 +01:00
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-03-27 05:22:22 +01:00
|
|
|
void *
|
|
|
|
ircd::ios::handler::allocate(handler *const &handler,
|
|
|
|
const size_t &size)
|
|
|
|
{
|
|
|
|
assert(handler && handler->descriptor);
|
|
|
|
auto &descriptor(*handler->descriptor);
|
2019-03-29 01:09:46 +01:00
|
|
|
|
|
|
|
assert(descriptor.stats);
|
|
|
|
auto &stats(*descriptor.stats);
|
|
|
|
stats.alloc_bytes += size;
|
|
|
|
++stats.allocs;
|
|
|
|
|
2019-03-27 10:49:28 +01:00
|
|
|
return descriptor.allocator(*handler, size);
|
2019-03-27 05:22:22 +01:00
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// ios.h
|
|
|
|
//
|
|
|
|
|
|
|
|
namespace ircd::ios
|
|
|
|
{
|
|
|
|
extern descriptor post_desc;
|
|
|
|
extern descriptor defer_desc;
|
|
|
|
extern descriptor dispatch_desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::ios::forking()
|
|
|
|
{
|
|
|
|
get().notify_fork(asio::execution_context::fork_prepare);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::ios::forked_child()
|
|
|
|
{
|
|
|
|
get().notify_fork(asio::execution_context::fork_child);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::ios::forked_parent()
|
|
|
|
{
|
|
|
|
get().notify_fork(asio::execution_context::fork_parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// dispatch
|
|
|
|
//
|
|
|
|
|
|
|
|
decltype(ircd::ios::dispatch_desc)
|
|
|
|
ircd::ios::dispatch_desc
|
|
|
|
{
|
|
|
|
"ircd::ios dispatch"
|
|
|
|
};
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::dispatch::dispatch(std::function<void ()> function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:dispatch
|
|
|
|
{
|
|
|
|
dispatch_desc, std::move(function)
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::dispatch::dispatch(synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:dispatch
|
|
|
|
{
|
|
|
|
dispatch_desc, synchronous, std::move(function)
|
|
|
|
}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::dispatch::dispatch(descriptor &descriptor,
|
|
|
|
synchronous_t)
|
|
|
|
:dispatch
|
|
|
|
{
|
|
|
|
dispatch_desc, synchronous, []
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::dispatch::dispatch(descriptor &descriptor,
|
|
|
|
synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
|
|
|
{
|
|
|
|
const ctx::uninterruptible::nothrow ui;
|
|
|
|
|
|
|
|
ctx::latch latch(1);
|
|
|
|
dispatch(descriptor, [&function, &latch]
|
|
|
|
{
|
|
|
|
const unwind uw{[&latch]
|
|
|
|
{
|
|
|
|
latch.count_down();
|
|
|
|
}};
|
|
|
|
|
|
|
|
function();
|
|
|
|
});
|
|
|
|
|
|
|
|
latch.wait();
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::dispatch::dispatch(descriptor &descriptor,
|
|
|
|
std::function<void ()> function)
|
|
|
|
{
|
|
|
|
boost::asio::dispatch(get(), handle(descriptor, std::move(function)));
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// defer
|
|
|
|
//
|
|
|
|
|
|
|
|
decltype(ircd::ios::defer_desc)
|
|
|
|
ircd::ios::defer_desc
|
|
|
|
{
|
|
|
|
"ircd::ios defer"
|
|
|
|
};
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::defer::defer(std::function<void ()> function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:defer
|
|
|
|
{
|
|
|
|
defer_desc, std::move(function)
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::defer::defer(synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:defer
|
|
|
|
{
|
|
|
|
defer_desc, synchronous, function
|
|
|
|
}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::defer::defer(descriptor &descriptor,
|
|
|
|
synchronous_t)
|
|
|
|
:defer
|
|
|
|
{
|
|
|
|
defer_desc, synchronous, []
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::defer::defer(descriptor &descriptor,
|
|
|
|
synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
|
|
|
{
|
|
|
|
const ctx::uninterruptible::nothrow ui;
|
|
|
|
|
|
|
|
ctx::latch latch(1);
|
|
|
|
defer(descriptor, [&function, &latch]
|
|
|
|
{
|
|
|
|
const unwind uw{[&latch]
|
|
|
|
{
|
|
|
|
latch.count_down();
|
|
|
|
}};
|
|
|
|
|
|
|
|
function();
|
|
|
|
});
|
|
|
|
|
|
|
|
latch.wait();
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::defer::defer(descriptor &descriptor,
|
|
|
|
std::function<void ()> function)
|
|
|
|
{
|
|
|
|
boost::asio::defer(get(), handle(descriptor, std::move(function)));
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// post
|
|
|
|
//
|
|
|
|
|
|
|
|
decltype(ircd::ios::post_desc)
|
|
|
|
ircd::ios::post_desc
|
|
|
|
{
|
|
|
|
"ircd::ios post"
|
|
|
|
};
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::post::post(std::function<void ()> function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:post
|
|
|
|
{
|
|
|
|
post_desc, std::move(function)
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::post::post(synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
2019-07-06 02:34:42 +02:00
|
|
|
:post
|
|
|
|
{
|
|
|
|
post_desc, synchronous, function
|
|
|
|
}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::post::post(descriptor &descriptor,
|
|
|
|
synchronous_t)
|
|
|
|
:post
|
|
|
|
{
|
|
|
|
descriptor, synchronous, []
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ircd::ios::post::post(descriptor &descriptor,
|
|
|
|
synchronous_t,
|
|
|
|
const std::function<void ()> &function)
|
|
|
|
{
|
|
|
|
const ctx::uninterruptible::nothrow ui;
|
|
|
|
|
|
|
|
ctx::latch latch(1);
|
|
|
|
post(descriptor, [&function, &latch]
|
|
|
|
{
|
|
|
|
const unwind uw{[&latch]
|
|
|
|
{
|
|
|
|
latch.count_down();
|
|
|
|
}};
|
|
|
|
|
|
|
|
function();
|
|
|
|
});
|
|
|
|
|
|
|
|
latch.wait();
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
ircd::ios::post::post(descriptor &descriptor,
|
|
|
|
std::function<void ()> function)
|
|
|
|
{
|
|
|
|
boost::asio::post(get(), handle(descriptor, std::move(function)));
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
boost::asio::io_context &
|
|
|
|
ircd::ios::get()
|
2019-08-06 01:15:56 +02:00
|
|
|
noexcept
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
assert(user);
|
|
|
|
return *user;
|
|
|
|
}
|
|
|
|
|
2019-09-11 00:11:25 +02:00
|
|
|
[[gnu::hot]]
|
2019-05-16 08:20:13 +02:00
|
|
|
bool
|
|
|
|
ircd::ios::available()
|
2019-08-06 01:15:56 +02:00
|
|
|
noexcept
|
2019-05-16 08:20:13 +02:00
|
|
|
{
|
|
|
|
return bool(user);
|
|
|
|
}
|