mirror of
https://github.com/tursom/GoCollections.git
synced 2024-12-25 23:40:12 +08:00
update Channel.go
This commit is contained in:
parent
c749131351
commit
c7e46ba377
@ -9,11 +9,13 @@ package concurrent
|
||||
type (
|
||||
Lock interface {
|
||||
Lock()
|
||||
TryLock() bool
|
||||
Unlock()
|
||||
}
|
||||
RWLock interface {
|
||||
Lock
|
||||
RLock()
|
||||
TryRLock() bool
|
||||
RUnlock()
|
||||
}
|
||||
)
|
||||
|
@ -29,6 +29,25 @@ func NewReentrantLock() *ReentrantLock {
|
||||
return res
|
||||
}
|
||||
|
||||
func (rt *ReentrantLock) TryLock() bool {
|
||||
id := GetGoroutineID()
|
||||
rt.lock.Lock()
|
||||
defer rt.lock.Unlock()
|
||||
|
||||
if rt.host == id {
|
||||
rt.recursion++
|
||||
return true
|
||||
}
|
||||
|
||||
if rt.recursion == 0 {
|
||||
rt.host = id
|
||||
rt.recursion = 1
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (rt *ReentrantLock) Lock() {
|
||||
id := GetGoroutineID()
|
||||
rt.lock.Lock()
|
||||
|
@ -48,6 +48,26 @@ func (rt *ReentrantRWLock) Lock() {
|
||||
rt.rlock.Lock()
|
||||
}
|
||||
|
||||
func (rt *ReentrantRWLock) TryLock() bool {
|
||||
id := GetGoroutineID()
|
||||
rt.lock.Lock()
|
||||
defer rt.lock.Unlock()
|
||||
|
||||
if rt.host == id {
|
||||
rt.recursion++
|
||||
return true
|
||||
}
|
||||
|
||||
if rt.recursion == 0 {
|
||||
rt.host = id
|
||||
rt.recursion = 1
|
||||
rt.rlock.Lock()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (rt *ReentrantRWLock) Unlock() {
|
||||
rt.lock.Lock()
|
||||
defer rt.lock.Unlock()
|
||||
@ -70,6 +90,13 @@ func (rt *ReentrantRWLock) RLock() {
|
||||
rt.rlock.RLock()
|
||||
}
|
||||
|
||||
func (rt *ReentrantRWLock) TryRLock() bool {
|
||||
if rt.host == GetGoroutineID() {
|
||||
return true
|
||||
}
|
||||
return rt.rlock.TryRLock()
|
||||
}
|
||||
|
||||
func (rt *ReentrantRWLock) RUnlock() {
|
||||
if rt.host == GetGoroutineID() {
|
||||
return
|
||||
|
@ -31,7 +31,7 @@ func TestMessageQueue_Subscribe(t *testing.T) {
|
||||
}()
|
||||
|
||||
for true {
|
||||
msg := subscribe.Receive()
|
||||
msg, _ := subscribe.Receive()
|
||||
fmt.Println(id, msg)
|
||||
}
|
||||
}()
|
||||
|
146
lang/Array.go
146
lang/Array.go
@ -23,8 +23,104 @@ func (a Array[T]) Array() []T {
|
||||
return a
|
||||
}
|
||||
|
||||
func FromRawInt8Array(arr []int8) Int8Array {
|
||||
return *(*Int8Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawInt16Array(arr []int16) Int16Array {
|
||||
return *(*Int16Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawInt32Array(arr []int32) Int32Array {
|
||||
return *(*Int32Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawInt64Array(arr []int64) Int64Array {
|
||||
return *(*Int64Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawUInt8Array(arr []uint8) UInt8Array {
|
||||
return *(*UInt8Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawUInt16Array(arr []uint16) UInt16Array {
|
||||
return *(*UInt16Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawUInt32Array(arr []uint32) UInt32Array {
|
||||
return *(*UInt32Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawUInt64Array(arr []uint64) UInt64Array {
|
||||
return *(*UInt64Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawFloat32Array(arr []float32) Float32Array {
|
||||
return *(*Float32Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawFloat64Array(arr []float64) Float64Array {
|
||||
return *(*Float64Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawComplex64Array(arr []complex64) Complex64Array {
|
||||
return *(*Complex64Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func FromRawComplex128Array(arr []complex128) Complex128Array {
|
||||
return *(*Complex128Array)(unsafe.Pointer(&arr))
|
||||
}
|
||||
|
||||
func (a UInt8Array) Bytes() []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&a))
|
||||
return a.Raw()
|
||||
}
|
||||
|
||||
func (a Int8Array) Raw() []int8 {
|
||||
return *(*[]int8)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Int16Array) Raw() []int16 {
|
||||
return *(*[]int16)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Int32Array) Raw() []int32 {
|
||||
return *(*[]int32)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Int64Array) Raw() []int64 {
|
||||
return *(*[]int64)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a UInt8Array) Raw() []uint8 {
|
||||
return *(*[]uint8)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a UInt16Array) Raw() []uint16 {
|
||||
return *(*[]uint16)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a UInt32Array) Raw() []uint32 {
|
||||
return *(*[]uint32)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a UInt64Array) Raw() []uint64 {
|
||||
return *(*[]uint64)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Float32Array) Raw() []float32 {
|
||||
return *(*[]float32)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Float64Array) Raw() []float64 {
|
||||
return *(*[]float64)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Complex64Array) Raw() []complex64 {
|
||||
return *(*[]complex64)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Complex128Array) Raw() []complex128 {
|
||||
return *(*[]complex128)(unsafe.Pointer(&a))
|
||||
}
|
||||
|
||||
func (a Int8Array) BitLength() uint {
|
||||
@ -91,34 +187,66 @@ func (a UInt64Array) GetBit(index uint) (ok bool) {
|
||||
return GetBit(a[index/64], index%64)
|
||||
}
|
||||
|
||||
func (a Int8Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a Int8Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/8], index%8, up)
|
||||
}
|
||||
|
||||
func (a Int16Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/16], index%16, up)
|
||||
}
|
||||
|
||||
func (a Int32Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/32], index%32, up)
|
||||
}
|
||||
|
||||
func (a Int64Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/64], index%64, up)
|
||||
}
|
||||
|
||||
func (a UInt8Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/8], index%8, up)
|
||||
}
|
||||
|
||||
func (a UInt16Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/16], index%16, up)
|
||||
}
|
||||
|
||||
func (a UInt32Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/32], index%32, up)
|
||||
}
|
||||
|
||||
func (a UInt64Array) SetBit(index uint, up bool) {
|
||||
SetBit(&a[index/64], index%64, up)
|
||||
}
|
||||
|
||||
func (a Int8Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/8], index%8, up)
|
||||
}
|
||||
|
||||
func (a Int16Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a Int16Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/16], index%16, up)
|
||||
}
|
||||
|
||||
func (a Int32Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a Int32Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/32], index%32, up)
|
||||
}
|
||||
|
||||
func (a Int64Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a Int64Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/64], index%64, up)
|
||||
}
|
||||
|
||||
func (a UInt8Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a UInt8Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/8], index%8, up)
|
||||
}
|
||||
|
||||
func (a UInt16Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a UInt16Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/16], index%16, up)
|
||||
}
|
||||
|
||||
func (a UInt32Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a UInt32Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/32], index%32, up)
|
||||
}
|
||||
|
||||
func (a UInt64Array) SetBit(index uint, up bool) (old bool) {
|
||||
func (a UInt64Array) SwapBit(index uint, up bool) (old bool) {
|
||||
return SwapBit(&a[index/64], index%64, up)
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ package lang
|
||||
type (
|
||||
BitSet interface {
|
||||
BitLength() uint
|
||||
SetBit(index uint, up bool) (old bool)
|
||||
SetBit(index uint, up bool)
|
||||
SwapBit(index uint, up bool) (old bool)
|
||||
GetBit(index uint) (ok bool)
|
||||
}
|
||||
)
|
||||
|
@ -19,8 +19,9 @@ type (
|
||||
|
||||
ReceiveChannel[T any] interface {
|
||||
Close()
|
||||
// RCh raw channel
|
||||
RCh() <-chan T
|
||||
Receive() T
|
||||
Receive() (T, bool)
|
||||
TryReceive() (T, bool)
|
||||
ReceiveTimeout(timeout time.Duration) (T, bool)
|
||||
}
|
||||
@ -102,8 +103,9 @@ func (ch RawChannel[T]) RCh() <-chan T {
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ch RawChannel[T]) Receive() T {
|
||||
return <-ch
|
||||
func (ch RawChannel[T]) Receive() (T, bool) {
|
||||
value, ok := <-ch
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (ch RawChannel[T]) TryReceive() (T, bool) {
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
package lang
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
convChannel[T1, T2 any] struct {
|
||||
@ -71,8 +73,13 @@ func (c *convChannel[T1, T2]) RCh() <-chan T2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *convChannel[T1, T2]) Receive() T2 {
|
||||
return c.recvConv(c.channel.Receive())
|
||||
func (c *convChannel[T1, T2]) Receive() (T2, bool) {
|
||||
value, ok := c.channel.Receive()
|
||||
if !ok {
|
||||
return Nil[T2](), false
|
||||
}
|
||||
|
||||
return c.recvConv(value), true
|
||||
}
|
||||
|
||||
func (c *convChannel[T1, T2]) TryReceive() (T2, bool) {
|
||||
@ -141,8 +148,13 @@ func (c *convReceiveChannel[T1, T2]) RCh() <-chan T2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *convReceiveChannel[T1, T2]) Receive() T2 {
|
||||
return c.recvConv(c.channel.Receive())
|
||||
func (c *convReceiveChannel[T1, T2]) Receive() (T2, bool) {
|
||||
value, ok := c.channel.Receive()
|
||||
if !ok {
|
||||
return Nil[T2](), false
|
||||
}
|
||||
|
||||
return c.recvConv(value), true
|
||||
}
|
||||
|
||||
func (c *convReceiveChannel[T1, T2]) TryReceive() (T2, bool) {
|
||||
@ -171,13 +183,13 @@ func (c *filterReceiveChannel[T]) RCh() <-chan T {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *filterReceiveChannel[T]) Receive() T {
|
||||
obj := f.ReceiveChannel.Receive()
|
||||
func (f *filterReceiveChannel[T]) Receive() (T, bool) {
|
||||
obj, _ := f.ReceiveChannel.Receive()
|
||||
for !f.filter(obj) {
|
||||
obj = f.ReceiveChannel.Receive()
|
||||
obj, _ = f.ReceiveChannel.Receive()
|
||||
}
|
||||
|
||||
return obj
|
||||
return obj, false
|
||||
}
|
||||
|
||||
func (f *filterReceiveChannel[T]) TryReceive() (T, bool) {
|
||||
|
@ -19,16 +19,34 @@ func GetBit[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
|
||||
return p&location != 0
|
||||
}
|
||||
|
||||
func SetBit[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
|
||||
Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64](p *T, index uint, new bool) {
|
||||
location := T(1) << index
|
||||
if new {
|
||||
*p |= location
|
||||
} else {
|
||||
*p &= ^location
|
||||
}
|
||||
}
|
||||
|
||||
func UpBit[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
|
||||
Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64](p *T, index uint) {
|
||||
*p |= T(1) << index
|
||||
}
|
||||
|
||||
func DownBit[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
|
||||
Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64](p *T, index uint) {
|
||||
*p &= ^(T(1) << index)
|
||||
}
|
||||
|
||||
func SwapBit[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 |
|
||||
Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64](p *T, index uint, new bool) (old bool) {
|
||||
location := T(1) << index
|
||||
oldValue := *p
|
||||
var newValue T
|
||||
if new {
|
||||
newValue = oldValue | location
|
||||
*p = oldValue | location
|
||||
} else {
|
||||
newValue = oldValue & ^location
|
||||
*p = oldValue & ^location
|
||||
}
|
||||
*p = newValue
|
||||
return oldValue&location != 0
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ package unsafe
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tursom/GoCollections/lang"
|
||||
)
|
||||
|
||||
type slice struct {
|
||||
@ -13,6 +11,16 @@ type slice struct {
|
||||
cap int
|
||||
}
|
||||
|
||||
type iface struct {
|
||||
tab *struct{}
|
||||
data unsafe.Pointer
|
||||
}
|
||||
|
||||
func Nil[T any]() T {
|
||||
var n T
|
||||
return n
|
||||
}
|
||||
|
||||
func ForceCast[T any](v unsafe.Pointer) *T {
|
||||
if v == nil {
|
||||
return nil
|
||||
@ -22,13 +30,13 @@ func ForceCast[T any](v unsafe.Pointer) *T {
|
||||
}
|
||||
|
||||
func Sizeof[T any]() uintptr {
|
||||
return unsafe.Sizeof(lang.Nil[T]())
|
||||
return unsafe.Sizeof(Nil[T]())
|
||||
}
|
||||
|
||||
// AsBytes cast any slice as []byte with same pinter, real len and real cap
|
||||
func AsBytes[T any](arr []T) []byte {
|
||||
sarr := *ForceCast[slice](unsafe.Pointer(&arr))
|
||||
typeAlign := reflect.TypeOf(lang.Nil[T]()).Align()
|
||||
typeAlign := reflect.TypeOf(Nil[T]()).Align()
|
||||
asBytes := unsafe.Pointer(&slice{
|
||||
array: sarr.array,
|
||||
len: sarr.len * typeAlign,
|
||||
@ -38,6 +46,36 @@ func AsBytes[T any](arr []T) []byte {
|
||||
return *ForceCast[[]byte](asBytes)
|
||||
}
|
||||
|
||||
func AsBytes1[T any](value *T) []byte {
|
||||
typeAlign := reflect.TypeOf(value).Align()
|
||||
asBytes := unsafe.Pointer(&slice{
|
||||
array: unsafe.Pointer(value),
|
||||
len: typeAlign,
|
||||
cap: typeAlign,
|
||||
})
|
||||
|
||||
return *ForceCast[[]byte](asBytes)
|
||||
}
|
||||
|
||||
func AsBytes2(value any) []byte {
|
||||
typeAlign := reflect.TypeOf(value).Align()
|
||||
asBytes := unsafe.Pointer(&slice{
|
||||
array: InterfacePointer(value),
|
||||
len: typeAlign,
|
||||
cap: typeAlign,
|
||||
})
|
||||
|
||||
return *ForceCast[[]byte](asBytes)
|
||||
}
|
||||
|
||||
func InterfacePointer(i any) unsafe.Pointer {
|
||||
return ForceCast[iface](unsafe.Pointer(&i)).data
|
||||
}
|
||||
|
||||
func InterfaceForceCast[T any](i any) *T {
|
||||
return ForceCast[T](ForceCast[iface](unsafe.Pointer(&i)).data)
|
||||
}
|
||||
|
||||
// AsString cast bytes as string
|
||||
func AsString(bytes []byte) string {
|
||||
return *ForceCast[string](unsafe.Pointer(&bytes))
|
||||
|
14
unsafe/unsafe_test.go
Normal file
14
unsafe/unsafe_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
package unsafe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestAsBytes1(t *testing.T) {
|
||||
i := 1
|
||||
var a any = i
|
||||
bs := AsBytes2(i)
|
||||
fmt.Println(i, bs, AsBytes1(&a), *(*int)(ForceCast[iface](unsafe.Pointer(&a)).data))
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package bloom
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
@ -10,7 +10,9 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBloom_Contains(t *testing.T) {
|
||||
@ -32,6 +34,51 @@ func TestBloom_Contains(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBloom_miss(t *testing.T) {
|
||||
//HashFunc = func(data []byte, seed uint32) uint32 {
|
||||
// return murmur3.Sum32WithSeed(data, seed)
|
||||
// // h1, _ := murmur3.Sum128WithSeed(data, seed)
|
||||
// // return uint32(h1)
|
||||
//}
|
||||
|
||||
var base uint = 1000_0000
|
||||
bloom := NewBloom(base, 0.03)
|
||||
|
||||
t1 := time.Now()
|
||||
|
||||
for i := 0; i < int(base/1000); i++ {
|
||||
bloom.Add([]byte(fmt.Sprintf("%d", i)))
|
||||
}
|
||||
|
||||
counter := make([]uint, 256)
|
||||
for _, value := range bloom.m {
|
||||
counter[value]++
|
||||
}
|
||||
|
||||
miss := 0
|
||||
for i := base; i < base*2; i++ {
|
||||
if bloom.Contains([]byte(fmt.Sprintf("%d", i))) {
|
||||
miss += 1
|
||||
}
|
||||
}
|
||||
|
||||
t2 := time.Now()
|
||||
|
||||
fmt.Println(miss, float64(miss)/float64(base))
|
||||
fmt.Println(counter)
|
||||
|
||||
var H float64
|
||||
for _, c := range counter {
|
||||
if c == 0 {
|
||||
continue
|
||||
}
|
||||
p := float64(c) / float64(len(bloom.m))
|
||||
H += -p * math.Log2(p)
|
||||
}
|
||||
fmt.Println(H / 8)
|
||||
fmt.Println(t2.Sub(t1))
|
||||
}
|
||||
|
||||
func gz(b []byte) []byte {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
|
||||
@ -43,8 +90,8 @@ func gz(b []byte) []byte {
|
||||
}
|
||||
|
||||
func TestCalcBitLength(t *testing.T) {
|
||||
//fmt.Printf("%d\n", CalcBitLength(100_0000, 0.1)/8)
|
||||
for i := 1; i < 63; i++ {
|
||||
//fmt.Printf("%d\n", CalcBitLength(1024*1024*1024, 0.03)/8)
|
||||
for i := 0; i < 63; i++ {
|
||||
var n uint = 1 << i
|
||||
numBytes := CalcBitLength(n, 0.1) / 8
|
||||
fmt.Printf("%d: %d, %s / %s = %f\n",
|
||||
|
23
util/bloom/hash.h
Normal file
23
util/bloom/hash.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include<stdint.h>
|
||||
|
||||
uint32_t hash(uint8_t* data, size_t size, uint32_t seed) {
|
||||
uint32_t hash = seed;
|
||||
switch (size % sizeof(uint32_t)) {
|
||||
case 3:
|
||||
hash = hash*31 ^ *data++;
|
||||
case 2:
|
||||
hash = hash*31 ^ *data++;
|
||||
case 1:
|
||||
hash = hash*31 ^ *data++;
|
||||
}
|
||||
|
||||
int n = size / sizeof(uint32_t);
|
||||
for (int i = 0; i < n; i++) {
|
||||
hash = hash*31 ^ *(uint32_t*)data;
|
||||
data += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
Loading…
Reference in New Issue
Block a user