mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
rename thread_local_ptr => tls::pointer
This commit is contained in:
parent
0285e2d6e1
commit
44ab812925
@ -22,7 +22,7 @@ HEADERS += \
|
|||||||
../include/ipc.h \
|
../include/ipc.h \
|
||||||
../include/def.h \
|
../include/def.h \
|
||||||
../include/rw_lock.h \
|
../include/rw_lock.h \
|
||||||
../include/thread_local_ptr.h
|
../include/tls_pointer.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
../src/shm.cpp \
|
../src/shm.cpp \
|
||||||
@ -31,7 +31,8 @@ SOURCES += \
|
|||||||
unix {
|
unix {
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
../src/platform/shm_linux.cpp
|
../src/platform/shm_linux.cpp \
|
||||||
|
../src/platform/tls_pointer_linux.cpp
|
||||||
|
|
||||||
LIBS += -lrt
|
LIBS += -lrt
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ else:win32 {
|
|||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
../src/platform/shm_win.cpp \
|
../src/platform/shm_win.cpp \
|
||||||
../src/platform/thread_local_ptr_win.cpp
|
../src/platform/tls_pointer_win.cpp
|
||||||
|
|
||||||
LIBS += -lKernel32
|
LIBS += -lKernel32
|
||||||
|
|
||||||
|
|||||||
@ -1,100 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility> // std::forward
|
|
||||||
|
|
||||||
#if defined(WINCE) || defined(_WIN32_WCE) || \
|
|
||||||
defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
|
||||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
|
||||||
#include <windows.h> // DWORD, ::Tls...
|
|
||||||
#define IPC_OS_WIN_
|
|
||||||
#else
|
|
||||||
#include <pthread.h> // pthread_...
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ipc {
|
|
||||||
|
|
||||||
#if defined(IPC_OS_WIN_)
|
|
||||||
|
|
||||||
#define IPC_THREAD_LOCAL_KEY_ DWORD
|
|
||||||
#define IPC_THREAD_LOCAL_SET(KEY, PTR) (::TlsSetValue(KEY, (LPVOID)PTR) == TRUE)
|
|
||||||
#define IPC_THREAD_LOCAL_GET(KEY) (::TlsGetValue(KEY))
|
|
||||||
|
|
||||||
void thread_local_create(IPC_THREAD_LOCAL_KEY_& key, void (*destructor)(void*));
|
|
||||||
void thread_local_delete(IPC_THREAD_LOCAL_KEY_ key);
|
|
||||||
|
|
||||||
#define IPC_THREAD_LOCAL_CREATE(KEY, DESTRUCTOR) thread_local_create(KEY, DESTRUCTOR)
|
|
||||||
#define IPC_THREAD_LOCAL_DELETE(KEY) thread_local_delete(KEY)
|
|
||||||
|
|
||||||
#else /*!IPC_OS_WIN_*/
|
|
||||||
|
|
||||||
#define IPC_THREAD_LOCAL_KEY_ pthread_key_t
|
|
||||||
#define IPC_THREAD_LOCAL_CREATE(KEY, DESTRUCTOR) pthread_key_create(&KEY, DESTRUCTOR)
|
|
||||||
#define IPC_THREAD_LOCAL_DELETE(KEY) pthread_key_delete(KEY)
|
|
||||||
#define IPC_THREAD_LOCAL_SET(KEY, PTR) (pthread_setspecific(KEY, (void*)PTR) == 0)
|
|
||||||
#define IPC_THREAD_LOCAL_GET(KEY) pthread_getspecific(KEY)
|
|
||||||
|
|
||||||
#endif/*!IPC_OS_WIN_*/
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
/// Thread-local pointer
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/*
|
|
||||||
<Remarks>
|
|
||||||
|
|
||||||
1. In Windows, if you do not compile thread_local_ptr.cpp,
|
|
||||||
use thread_local_ptr will cause memory leaks.
|
|
||||||
|
|
||||||
2. You need to set the thread_local_ptr's storage manually:
|
|
||||||
```
|
|
||||||
thread_local_ptr<int> p;
|
|
||||||
if (!p) p = new int(123);
|
|
||||||
```
|
|
||||||
Just like an ordinary pointer. Or you could just call create:
|
|
||||||
```
|
|
||||||
thread_local_ptr<int> p;
|
|
||||||
p.create(123);
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class thread_local_ptr {
|
|
||||||
IPC_THREAD_LOCAL_KEY_ key_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
using value_type = T;
|
|
||||||
|
|
||||||
thread_local_ptr() {
|
|
||||||
IPC_THREAD_LOCAL_CREATE(key_, [](void* p) {
|
|
||||||
delete static_cast<T*>(p);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
~thread_local_ptr() {
|
|
||||||
IPC_THREAD_LOCAL_DELETE(key_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... P>
|
|
||||||
T* create(P&&... params) {
|
|
||||||
auto ptr = static_cast<T*>(*this);
|
|
||||||
if (ptr == nullptr) {
|
|
||||||
return (*this) = new T(std::forward<P>(params)...);
|
|
||||||
}
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
T* operator=(T* ptr) {
|
|
||||||
IPC_THREAD_LOCAL_SET(key_, ptr);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator T*() const { return static_cast<T*>(IPC_THREAD_LOCAL_GET(key_)); }
|
|
||||||
|
|
||||||
T& operator* () { return *static_cast<T*>(*this); }
|
|
||||||
const T& operator* () const { return *static_cast<T*>(*this); }
|
|
||||||
|
|
||||||
T* operator->() { return static_cast<T*>(*this); }
|
|
||||||
const T* operator->() const { return static_cast<T*>(*this); }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ipc
|
|
||||||
84
include/tls_pointer.h
Normal file
84
include/tls_pointer.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace ipc {
|
||||||
|
namespace tls {
|
||||||
|
|
||||||
|
using key_t = std::uint64_t;
|
||||||
|
using destructor_t = void (*)(void*);
|
||||||
|
|
||||||
|
enum : key_t {
|
||||||
|
invalid_value = (std::numeric_limits<key_t>::max)()
|
||||||
|
};
|
||||||
|
|
||||||
|
key_t create (destructor_t destructor = nullptr);
|
||||||
|
void release(key_t key);
|
||||||
|
|
||||||
|
bool set(key_t key, void* ptr);
|
||||||
|
void* get(key_t key);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
/// Thread-local pointer
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
<Remarks>
|
||||||
|
|
||||||
|
1. In Windows, if you do not compile thread_local_ptr.cpp,
|
||||||
|
use thread_local_ptr will cause memory leaks.
|
||||||
|
|
||||||
|
2. You need to set the thread_local_ptr's storage manually:
|
||||||
|
```
|
||||||
|
thread_local_ptr<int> p;
|
||||||
|
if (!p) p = new int(123);
|
||||||
|
```
|
||||||
|
Just like an ordinary pointer. Or you could just call create:
|
||||||
|
```
|
||||||
|
thread_local_ptr<int> p;
|
||||||
|
p.create(123);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class pointer {
|
||||||
|
key_t key_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
pointer() {
|
||||||
|
key_ = tls::create([](void* p) { delete static_cast<T*>(p); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~pointer() {
|
||||||
|
tls::release(key_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... P>
|
||||||
|
T* create(P&&... params) {
|
||||||
|
auto ptr = static_cast<T*>(*this);
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
return (*this) = new T { std::forward<P>(params)... };
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* operator=(T* ptr) {
|
||||||
|
set(key_, ptr);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T*() const { return static_cast<T*>(get(key_)); }
|
||||||
|
|
||||||
|
T& operator* () { return *static_cast<T*>(*this); }
|
||||||
|
const T& operator* () const { return *static_cast<T*>(*this); }
|
||||||
|
|
||||||
|
T* operator->() { return static_cast<T*>(*this); }
|
||||||
|
const T* operator->() const { return static_cast<T*>(*this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tls
|
||||||
|
} // namespace ipc
|
||||||
@ -13,7 +13,7 @@
|
|||||||
#include "def.h"
|
#include "def.h"
|
||||||
#include "circ_queue.h"
|
#include "circ_queue.h"
|
||||||
#include "rw_lock.h"
|
#include "rw_lock.h"
|
||||||
#include "thread_local_ptr.h"
|
#include "tls_pointer.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ using guard_t = std::unique_ptr<std::remove_pointer_t<handle_t>, void(*)(handle_
|
|||||||
* https://sourceforge.net/p/mingw-w64/bugs/727/
|
* https://sourceforge.net/p/mingw-w64/bugs/727/
|
||||||
*/
|
*/
|
||||||
/*thread_local*/
|
/*thread_local*/
|
||||||
thread_local_ptr<std::unordered_map<decltype(msg_t::id_), std::vector<byte_t>>> recv_caches__;
|
tls::pointer<std::unordered_map<decltype(msg_t::id_), std::vector<byte_t>>> recv_caches__;
|
||||||
|
|
||||||
std::unordered_map<handle_t, queue_t> h2q__;
|
std::unordered_map<handle_t, queue_t> h2q__;
|
||||||
rw_lock h2q_lc__;
|
rw_lock h2q_lc__;
|
||||||
|
|||||||
29
src/platform/tls_pointer_linux.cpp
Normal file
29
src/platform/tls_pointer_linux.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "tls_pointer.h"
|
||||||
|
|
||||||
|
#include <pthread.h> // pthread_...
|
||||||
|
|
||||||
|
namespace ipc {
|
||||||
|
namespace tls {
|
||||||
|
|
||||||
|
key_t create(destructor_t destructor) {
|
||||||
|
pthread_key_t k;
|
||||||
|
if (pthread_key_create(&k, destructor) == 0) {
|
||||||
|
return static_cast<key_t>(k);
|
||||||
|
}
|
||||||
|
return invalid_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(key_t key) {
|
||||||
|
pthread_key_delete(static_cast<pthread_key_t>(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set(key_t key, void* ptr) {
|
||||||
|
return pthread_setspecific(static_cast<pthread_key_t>(key), ptr) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* get(key_t key) {
|
||||||
|
return pthread_getspecific(static_cast<pthread_key_t>(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tls
|
||||||
|
} // namespace ipc
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "thread_local_ptr.h"
|
#include "tls_pointer.h"
|
||||||
|
|
||||||
#include <windows.h> // ::Tls...
|
#include <windows.h> // ::Tls...
|
||||||
#include <unordered_map> // std::unordered_map
|
#include <unordered_map> // std::unordered_map
|
||||||
@ -18,71 +18,85 @@ namespace ipc {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct tls_data {
|
|
||||||
using destructor_t = void(*)(void*);
|
|
||||||
using map_t = std::unordered_map<IPC_THREAD_LOCAL_KEY_, tls_data>;
|
|
||||||
|
|
||||||
static DWORD& key() {
|
struct tls_data {
|
||||||
static IPC_THREAD_LOCAL_KEY_ rec_key = ::TlsAlloc();
|
using destructor_t = void(*)(void*);
|
||||||
return rec_key;
|
using map_t = std::unordered_map<tls::key_t, tls_data>;
|
||||||
}
|
|
||||||
|
|
||||||
static map_t* records(map_t* rec) {
|
static DWORD& key() {
|
||||||
IPC_THREAD_LOCAL_SET(key(), rec);
|
static DWORD rec_key = ::TlsAlloc();
|
||||||
return rec;
|
return rec_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
static map_t* records() {
|
static map_t* records(map_t* rec) {
|
||||||
return static_cast<map_t*>(IPC_THREAD_LOCAL_GET(key()));
|
::TlsSetValue(key(), static_cast<LPVOID>(rec));
|
||||||
}
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
IPC_THREAD_LOCAL_KEY_ key_ = 0;
|
static map_t* records() {
|
||||||
destructor_t destructor_ = nullptr;
|
return static_cast<map_t*>(::TlsGetValue(key()));
|
||||||
|
}
|
||||||
|
|
||||||
tls_data() = default;
|
tls::key_t key_ = tls::invalid_value;
|
||||||
|
destructor_t destructor_ = nullptr;
|
||||||
|
|
||||||
tls_data(IPC_THREAD_LOCAL_KEY_ key, destructor_t destructor)
|
tls_data() = default;
|
||||||
: key_ (key)
|
|
||||||
, destructor_(destructor)
|
|
||||||
{}
|
|
||||||
|
|
||||||
tls_data(tls_data&& rhs) : tls_data() {
|
tls_data(tls::key_t key, destructor_t destructor)
|
||||||
(*this) = std::move(rhs);
|
: key_ (key)
|
||||||
}
|
, destructor_(destructor)
|
||||||
|
{}
|
||||||
|
|
||||||
tls_data& operator=(tls_data&& rhs) {
|
tls_data(tls_data&& rhs) : tls_data() {
|
||||||
key_ = rhs.key_;
|
(*this) = std::move(rhs);
|
||||||
destructor_ = rhs.destructor_;
|
}
|
||||||
rhs.key_ = 0;
|
|
||||||
rhs.destructor_ = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~tls_data() {
|
tls_data& operator=(tls_data&& rhs) {
|
||||||
if (destructor_) destructor_(IPC_THREAD_LOCAL_GET(key_));
|
key_ = rhs.key_;
|
||||||
}
|
destructor_ = rhs.destructor_;
|
||||||
};
|
rhs.key_ = 0;
|
||||||
}
|
rhs.destructor_ = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void thread_local_create(IPC_THREAD_LOCAL_KEY_& key, void (*destructor)(void*)) {
|
~tls_data() {
|
||||||
key = ::TlsAlloc();
|
if (destructor_) destructor_(tls::get(key_));
|
||||||
if (key == TLS_OUT_OF_INDEXES) return;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // internal-linkage
|
||||||
|
|
||||||
|
namespace tls {
|
||||||
|
|
||||||
|
key_t create(destructor_t destructor) {
|
||||||
|
key_t key = static_cast<key_t>(::TlsAlloc());
|
||||||
|
if (key == TLS_OUT_OF_INDEXES) return invalid_value;
|
||||||
auto rec = tls_data::records();
|
auto rec = tls_data::records();
|
||||||
if (!rec) rec = tls_data::records(new tls_data::map_t);
|
if (!rec) rec = tls_data::records(new tls_data::map_t);
|
||||||
if (!rec) return;
|
if (!rec) return key;
|
||||||
rec->emplace(key, tls_data{ key, destructor });
|
rec->emplace(key, tls_data{ key, destructor });
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
void thread_local_delete(IPC_THREAD_LOCAL_KEY_ key) {
|
void release(key_t key) {
|
||||||
auto rec = tls_data::records();
|
auto rec = tls_data::records();
|
||||||
if (!rec) return;
|
if (!rec) return;
|
||||||
rec->erase(key);
|
rec->erase(key);
|
||||||
::TlsFree(key);
|
::TlsFree(static_cast<DWORD>(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
bool set(key_t key, void* ptr) {
|
||||||
/// Call destructors on thread exit
|
return ::TlsSetValue(static_cast<DWORD>(key),
|
||||||
////////////////////////////////////////////////////////////////
|
static_cast<LPVOID>(ptr)) == TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* get(key_t key) {
|
||||||
|
return static_cast<void*>(::TlsGetValue(static_cast<DWORD>(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tls
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
void OnThreadExit() {
|
void OnThreadExit() {
|
||||||
auto rec = tls_data::records();
|
auto rec = tls_data::records();
|
||||||
@ -95,6 +109,12 @@ void NTAPI OnTlsCallback(PVOID, DWORD dwReason, PVOID) {
|
|||||||
if (dwReason == DLL_THREAD_DETACH) OnThreadExit();
|
if (dwReason == DLL_THREAD_DETACH) OnThreadExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // internal-linkage
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
/// Call destructors on thread exit
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
|
|
||||||
#if defined(IPC_OS_WIN64_)
|
#if defined(IPC_OS_WIN64_)
|
||||||
@ -102,8 +122,7 @@ void NTAPI OnTlsCallback(PVOID, DWORD dwReason, PVOID) {
|
|||||||
#pragma comment(linker, "/INCLUDE:_tls_used")
|
#pragma comment(linker, "/INCLUDE:_tls_used")
|
||||||
#pragma comment(linker, "/INCLUDE:_tls_xl_b__")
|
#pragma comment(linker, "/INCLUDE:_tls_xl_b__")
|
||||||
|
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
# pragma const_seg(".CRT$XLB")
|
# pragma const_seg(".CRT$XLB")
|
||||||
extern const PIMAGE_TLS_CALLBACK _tls_xl_b__;
|
extern const PIMAGE_TLS_CALLBACK _tls_xl_b__;
|
||||||
const PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
const PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||||
@ -115,8 +134,7 @@ extern "C"
|
|||||||
#pragma comment(linker, "/INCLUDE:__tls_used")
|
#pragma comment(linker, "/INCLUDE:__tls_used")
|
||||||
#pragma comment(linker, "/INCLUDE:__tls_xl_b__")
|
#pragma comment(linker, "/INCLUDE:__tls_xl_b__")
|
||||||
|
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
# pragma data_seg(".CRT$XLB")
|
# pragma data_seg(".CRT$XLB")
|
||||||
PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||||
# pragma data_seg()
|
# pragma data_seg()
|
||||||
@ -131,15 +149,13 @@ extern "C"
|
|||||||
#if defined(__MINGW64__) || (__MINGW64_VERSION_MAJOR) || \
|
#if defined(__MINGW64__) || (__MINGW64_VERSION_MAJOR) || \
|
||||||
(__MINGW32_MAJOR_VERSION > 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION >= 18))
|
(__MINGW32_MAJOR_VERSION > 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION >= 18))
|
||||||
|
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else /*!__MINGW*/
|
#else /*!__MINGW*/
|
||||||
|
|
||||||
extern "C"
|
extern "C" {
|
||||||
{
|
|
||||||
ULONG _tls_index__ = 0;
|
ULONG _tls_index__ = 0;
|
||||||
|
|
||||||
IPC_CRTALLOC__(".tls$AAA") char _tls_start__ = 0;
|
IPC_CRTALLOC__(".tls$AAA") char _tls_start__ = 0;
|
||||||
@ -150,8 +166,7 @@ extern "C"
|
|||||||
IPC_CRTALLOC__(".CRT$XLZ") PIMAGE_TLS_CALLBACK _tls_xl_z__ = 0;
|
IPC_CRTALLOC__(".CRT$XLZ") PIMAGE_TLS_CALLBACK _tls_xl_z__ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" NX_CRTALLOC_(".tls") const IMAGE_TLS_DIRECTORY _tls_used =
|
extern "C" NX_CRTALLOC_(".tls") const IMAGE_TLS_DIRECTORY _tls_used = {
|
||||||
{
|
|
||||||
(ULONG_PTR)(&_tls_start__ + 1),
|
(ULONG_PTR)(&_tls_start__ + 1),
|
||||||
(ULONG_PTR) &_tls_end__,
|
(ULONG_PTR) &_tls_end__,
|
||||||
(ULONG_PTR) &_tls_index__,
|
(ULONG_PTR) &_tls_index__,
|
||||||
Loading…
x
Reference in New Issue
Block a user