mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
49
common/signal/done/done.go
Normal file
49
common/signal/done/done.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package done
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Instance is a utility for notifications of something being done.
|
||||
type Instance struct {
|
||||
access sync.Mutex
|
||||
c chan struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
// New returns a new Done.
|
||||
func New() *Instance {
|
||||
return &Instance{
|
||||
c: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Done returns true if Close() is called.
|
||||
func (d *Instance) Done() bool {
|
||||
select {
|
||||
case <-d.Wait():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Wait returns a channel for waiting for done.
|
||||
func (d *Instance) Wait() <-chan struct{} {
|
||||
return d.c
|
||||
}
|
||||
|
||||
// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.
|
||||
func (d *Instance) Close() error {
|
||||
d.access.Lock()
|
||||
defer d.access.Unlock()
|
||||
|
||||
if d.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.closed = true
|
||||
close(d.c)
|
||||
|
||||
return nil
|
||||
}
|
26
common/signal/notifier.go
Normal file
26
common/signal/notifier.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package signal
|
||||
|
||||
// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.
|
||||
type Notifier struct {
|
||||
c chan struct{}
|
||||
}
|
||||
|
||||
// NewNotifier creates a new Notifier.
|
||||
func NewNotifier() *Notifier {
|
||||
return &Notifier{
|
||||
c: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Signal signals a change, usually by producer. This method never blocks.
|
||||
func (n *Notifier) Signal() {
|
||||
select {
|
||||
case n.c <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Wait returns a channel for waiting for changes. The returned channel never gets closed.
|
||||
func (n *Notifier) Wait() <-chan struct{} {
|
||||
return n.c
|
||||
}
|
20
common/signal/notifier_test.go
Normal file
20
common/signal/notifier_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package signal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/common/signal"
|
||||
)
|
||||
|
||||
func TestNotifierSignal(t *testing.T) {
|
||||
n := NewNotifier()
|
||||
|
||||
w := n.Wait()
|
||||
n.Signal()
|
||||
|
||||
select {
|
||||
case <-w:
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
}
|
106
common/signal/pubsub/pubsub.go
Normal file
106
common/signal/pubsub/pubsub.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package pubsub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/signal/done"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
)
|
||||
|
||||
type Subscriber struct {
|
||||
buffer chan interface{}
|
||||
done *done.Instance
|
||||
}
|
||||
|
||||
func (s *Subscriber) push(msg interface{}) {
|
||||
select {
|
||||
case s.buffer <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscriber) Wait() <-chan interface{} {
|
||||
return s.buffer
|
||||
}
|
||||
|
||||
func (s *Subscriber) Close() error {
|
||||
return s.done.Close()
|
||||
}
|
||||
|
||||
func (s *Subscriber) IsClosed() bool {
|
||||
return s.done.Done()
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
sync.RWMutex
|
||||
subs map[string][]*Subscriber
|
||||
ctask *task.Periodic
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
s := &Service{
|
||||
subs: make(map[string][]*Subscriber),
|
||||
}
|
||||
s.ctask = &task.Periodic{
|
||||
Execute: s.Cleanup,
|
||||
Interval: time.Second * 30,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Cleanup cleans up internal caches of subscribers.
|
||||
// Visible for testing only.
|
||||
func (s *Service) Cleanup() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.subs) == 0 {
|
||||
return errors.New("nothing to do")
|
||||
}
|
||||
|
||||
for name, subs := range s.subs {
|
||||
newSub := make([]*Subscriber, 0, len(s.subs))
|
||||
for _, sub := range subs {
|
||||
if !sub.IsClosed() {
|
||||
newSub = append(newSub, sub)
|
||||
}
|
||||
}
|
||||
if len(newSub) == 0 {
|
||||
delete(s.subs, name)
|
||||
} else {
|
||||
s.subs[name] = newSub
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.subs) == 0 {
|
||||
s.subs = make(map[string][]*Subscriber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Subscribe(name string) *Subscriber {
|
||||
sub := &Subscriber{
|
||||
buffer: make(chan interface{}, 16),
|
||||
done: done.New(),
|
||||
}
|
||||
s.Lock()
|
||||
subs := append(s.subs[name], sub)
|
||||
s.subs[name] = subs
|
||||
s.Unlock()
|
||||
common.Must(s.ctask.Start())
|
||||
return sub
|
||||
}
|
||||
|
||||
func (s *Service) Publish(name string, message interface{}) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
for _, sub := range s.subs[name] {
|
||||
if !sub.IsClosed() {
|
||||
sub.push(message)
|
||||
}
|
||||
}
|
||||
}
|
34
common/signal/pubsub/pubsub_test.go
Normal file
34
common/signal/pubsub/pubsub_test.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package pubsub_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/common/signal/pubsub"
|
||||
)
|
||||
|
||||
func TestPubsub(t *testing.T) {
|
||||
service := NewService()
|
||||
|
||||
sub := service.Subscribe("a")
|
||||
service.Publish("a", 1)
|
||||
|
||||
select {
|
||||
case v := <-sub.Wait():
|
||||
if v != 1 {
|
||||
t.Error("expected subscribed value 1, but got ", v)
|
||||
}
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
sub.Close()
|
||||
service.Publish("a", 2)
|
||||
|
||||
select {
|
||||
case <-sub.Wait():
|
||||
t.Fail()
|
||||
default:
|
||||
}
|
||||
|
||||
service.Cleanup()
|
||||
}
|
27
common/signal/semaphore/semaphore.go
Normal file
27
common/signal/semaphore/semaphore.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package semaphore
|
||||
|
||||
// Instance is an implementation of semaphore.
|
||||
type Instance struct {
|
||||
token chan struct{}
|
||||
}
|
||||
|
||||
// New create a new Semaphore with n permits.
|
||||
func New(n int) *Instance {
|
||||
s := &Instance{
|
||||
token: make(chan struct{}, n),
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
s.token <- struct{}{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Wait returns a channel for acquiring a permit.
|
||||
func (s *Instance) Wait() <-chan struct{} {
|
||||
return s.token
|
||||
}
|
||||
|
||||
// Signal releases a permit into the semaphore.
|
||||
func (s *Instance) Signal() {
|
||||
s.token <- struct{}{}
|
||||
}
|
82
common/signal/timer.go
Normal file
82
common/signal/timer.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package signal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/task"
|
||||
)
|
||||
|
||||
type ActivityUpdater interface {
|
||||
Update()
|
||||
}
|
||||
|
||||
type ActivityTimer struct {
|
||||
sync.RWMutex
|
||||
updated chan struct{}
|
||||
checkTask *task.Periodic
|
||||
onTimeout func()
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) Update() {
|
||||
select {
|
||||
case t.updated <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) check() error {
|
||||
select {
|
||||
case <-t.updated:
|
||||
default:
|
||||
t.finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) finish() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.onTimeout != nil {
|
||||
t.onTimeout()
|
||||
t.onTimeout = nil
|
||||
}
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
t.checkTask = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
||||
if timeout == 0 {
|
||||
t.finish()
|
||||
return
|
||||
}
|
||||
|
||||
checkTask := &task.Periodic{
|
||||
Interval: timeout,
|
||||
Execute: t.check,
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
}
|
||||
t.checkTask = checkTask
|
||||
t.Unlock()
|
||||
t.Update()
|
||||
common.Must(checkTask.Start())
|
||||
}
|
||||
|
||||
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
||||
timer := &ActivityTimer{
|
||||
updated: make(chan struct{}, 1),
|
||||
onTimeout: cancel,
|
||||
}
|
||||
timer.SetTimeout(timeout)
|
||||
return timer
|
||||
}
|
60
common/signal/timer_test.go
Normal file
60
common/signal/timer_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package signal_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/common/signal"
|
||||
)
|
||||
|
||||
func TestActivityTimer(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
timer := CancelAfterInactivity(ctx, cancel, time.Second*4)
|
||||
time.Sleep(time.Second * 6)
|
||||
if ctx.Err() == nil {
|
||||
t.Error("expected some error, but got nil")
|
||||
}
|
||||
runtime.KeepAlive(timer)
|
||||
}
|
||||
|
||||
func TestActivityTimerUpdate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
timer := CancelAfterInactivity(ctx, cancel, time.Second*10)
|
||||
time.Sleep(time.Second * 3)
|
||||
if ctx.Err() != nil {
|
||||
t.Error("expected nil, but got ", ctx.Err().Error())
|
||||
}
|
||||
timer.SetTimeout(time.Second * 1)
|
||||
time.Sleep(time.Second * 2)
|
||||
if ctx.Err() == nil {
|
||||
t.Error("expcted some error, but got nil")
|
||||
}
|
||||
runtime.KeepAlive(timer)
|
||||
}
|
||||
|
||||
func TestActivityTimerNonBlocking(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
timer := CancelAfterInactivity(ctx, cancel, 0)
|
||||
time.Sleep(time.Second * 1)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
t.Error("context not done")
|
||||
}
|
||||
timer.SetTimeout(0)
|
||||
timer.SetTimeout(1)
|
||||
timer.SetTimeout(2)
|
||||
}
|
||||
|
||||
func TestActivityTimerZeroTimeout(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
timer := CancelAfterInactivity(ctx, cancel, 0)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
t.Error("context not done")
|
||||
}
|
||||
runtime.KeepAlive(timer)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue