From 65dbd2b8d2486fb4a33b4bee992c7e6dd0384a7f Mon Sep 17 00:00:00 2001 From: free will <2647778488@qq.com> Date: Thu, 23 Jun 2022 11:30:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BF=A1=E5=8F=B7=E9=87=8F?= =?UTF-8?q?=EF=BC=88=E5=AE=9E=E7=8E=B0=E8=87=AAMINSync=EF=BC=8Ccp=E5=88=B0?= =?UTF-8?q?=E8=BF=99=E9=87=8C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 4 + utils/Signal.go | 287 +++++++++++++++++++++++++++++++++++ utils/Signal_test.go | 354 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 646 insertions(+) create mode 100644 utils/Signal.go create mode 100644 utils/Signal_test.go diff --git a/go.mod b/go.mod index 0629455..b7fd07b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( //github.com/rakanalh/scheduler v0.1 github.com/go-co-op/gocron v1.13.0 + github.com/liyue201/gostl v1.0.1 github.com/smartystreets/goconvey v1.7.2 // indirect github.com/tidwall/btree v1.2.1 // indirect github.com/tidwall/buntdb v1.2.9 diff --git a/go.sum b/go.sum index d83584c..4583ce7 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/liyue201/gostl v1.0.1 h1:VQdvogZ90WpCb5WdG9UxS6r5ulnYEp8VfEMEZXVtpIs= +github.com/liyue201/gostl v1.0.1/go.mod h1:rgK+T1a0sQ1+CsAonfuD1J8C4iuGfOU9VAt9mmR/m10= github.com/mutecomm/go-sqlcipher/v4 v4.4.2 h1:eM10bFtI4UvibIsKr10/QT7Yfz+NADfjZYh0GKrXUNc= github.com/mutecomm/go-sqlcipher/v4 v4.4.2/go.mod h1:mF2UmIpBnzFeBdu/ypTDb/LdbS0nk0dfSN1WUsWTjMA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -30,6 +32,7 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= @@ -71,6 +74,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= diff --git a/utils/Signal.go b/utils/Signal.go new file mode 100644 index 0000000..54dfc49 --- /dev/null +++ b/utils/Signal.go @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2014-2022, Peking University Shenzhen Graduate School + * + * This file is part of MIN-Sync. + * See AUTHORS.md for complete list of MIN-Sync authors and contributors. + * + * MIN-Sync is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * MIN-Sync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * MIN-Sync, e.g., in COPYING.md file. If not, see . + * + + * This file incorporates work covered by the following copyright and + * permission notice: + + * The MIT License (MIT) + + * Copyright (c) 2000 Arash Partow + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package utils + +import ( + "minlib/common" + "reflect" + + "github.com/liyue201/gostl/ds/vector" +) + +type DisconnectFunction = func() + +/** \brief represents a connection to a signal + * \note This type is copyable. Any copy can be used to disconnect. + */ +type Connection struct { + disconnect DisconnectFunction +} + +/** \brief disconnects from the signal + * \note If the connection is already disconnected, or if the Signal has been cleared, + * this operation has no effect. + * \warning During signal emission, attempting to disconnect a connection other than + * the executing handler's own connection results in undefined behavior. + */ +func (c *Connection) Disconnect() { + if c.IsConnected() { + c.disconnect() + } +} + +/** \brief check if connected to the signal + */ +func (c *Connection) IsConnected() bool { + return c.disconnect != nil +} + +/** \brief compare for equality + * + * Two connections are equal if they both point to the same connection that isn't disconnected, + * or they are both disconnected. + */ +func (c *Connection) Equals(other *Connection) bool { + return (!c.IsConnected() && !other.IsConnected()) || reflect.ValueOf(c.disconnect).Pointer() == reflect.ValueOf(other.disconnect).Pointer() +} + +/* Factory Method for Connection */ +func NewConnectionByDisconnectFunc(df DisconnectFunction) *Connection { + c := new(Connection) + c.disconnect = df + return c +} + +/** \brief represents a function that can connect to the signal + */ +type Handler = func(...interface{}) + +/** \brief provides a lightweight signal / event system + * + * To declare a signal: + * factory method; + * To connect to a signal: + * owner.signalName.connect(f); + * Multiple functions can connect to the same signal. + * To emit a signal from owner: + * this->signalName.emit(arg1, arg2); + * + * \sa signal-emit.hpp allows owner's derived classes to emit signals + */ + +type Signal struct { + slots SlotList + /** \brief is a signal handler executing? + */ + isExecuting bool + currentSlot SlotIterator +} + +/** \brief connects a handler to the signal + * \note If invoked from a handler, the new handler won't receive the current emitted signal. + * \warning The handler is permitted to disconnect itself, but it must ensure its validity. + */ +func (s *Signal) Connect(handler Handler) *Connection { + slot := NewSlotByHandler(handler) + slotIterator := s.slots.Insert(s.slots.End(), slot) + + /* disconnect management */ + df := func() { + s.disconnect(slotIterator) + } + slot.Disconnect = df /* strong reference */ + weakPtr := NewConnectionByDisconnectFunc(df) /* weak reference */ + slot.Connection = weakPtr /* slot takes over connection */ + return weakPtr +} + +/** \brief connects a single-shot handler to the signal + * + * After the handler is executed once, it is automatically disconnected. + */ +func (s *Signal) ConnectSingleShot(handler Handler) *Connection { + slot := new(Slot) + slotIterator := s.slots.Insert(s.slots.End(), slot) + + df := func() { + s.disconnect(slotIterator) + } + conn := NewConnectionByDisconnectFunc(df) + + slot.Disconnect = df + slot.Connection = conn + slot.Handler = func(args ...interface{}) { + handler(args) + conn.Disconnect() + } + + return conn +} + +/** \retval true if there is no connection + */ +func (s *Signal) IsEmpty() bool { + return !s.isExecuting && s.slots.Empty() +} + +/** \brief emits a signal + * \param args arguments passed to all handlers + * \warning Emitting the signal from a handler is undefined behavior. + * \note If a handler throws, the exception will be propagated to the caller + * who emits this signal, and some handlers may not be executed. + */ +func (s *Signal) Emit(args ...interface{}) { + if s.IsEmpty() { + return + } + + defer func() { + s.isExecuting = false + }() + s.isExecuting = true + + it := s.slots.Begin() + last := s.slots.Last() + isLast := false + for !isLast { + isLast = it.Equal(last) + s.currentSlot = it + s.currentSlot.Value().(*Slot).Handler(args...) + + // disconnect while executing current slot + if s.currentSlot.Equal(s.slots.End()) { + it = s.slots.Erase(it) + } else { + it = it.Next().(SlotIterator) + } + } +} + +/** \brief disconnects the handler in a slot + */ +func (s *Signal) disconnect(iterator SlotIterator) { + if s.isExecuting { + // during signal emission, only the currently executing handler can be disconnected + if !s.currentSlot.Equal(iterator) { + common.LogWarn("cannot disconnect another handler from a handler") + return + } + + // this serves to indicate that the current slot needs to be erased from the list + // after it finishes executing; we cannot do it here because of bug #2333 + s.currentSlot = s.slots.End() + // destruct + iterator.Value().(*Slot).Destruct() + } else { + iterator.Value().(*Slot).Destruct() + s.slots.Erase(iterator) + } +} + +/* Clear + */ +func (s *Signal) Clear() { + /* \warn should only called when signal is not executing */ + s.currentSlot = nil + + /* clean all slots */ + it := s.slots.Begin() + for !it.Equal(s.slots.End()) { + it.Value().(*Slot).Destruct() + it = it.Next().(*vector.VectorIterator) + } + + /* clean slots container */ + s.slots.Clear() + // s.slots = nil is not needed +} + +/* factory method for Signal */ +func NewSignal() *Signal { + s := new(Signal) + s.isExecuting = false + s.slots = vector.New() + return s +} + +type SlotList = *vector.Vector + +type SlotIterator = *vector.VectorIterator + +type Slot struct { + /** \brief weak reference of disconnect function + connection's lifetime is taken over by slot + */ + *Connection + /** \brief the handler function who will receive emitted signals + */ + Handler Handler + /** \brief the disconnect function which will disconnect this handler + * + * In practice this is the Signal.Disconnect method bound to a ptr + * pointing at this slot. + * + * Connection also has a ptr which references the same function object. + * When the slot is erased or the signal is destructed, this function object is + * destructed, and the related Connections cannot disconnect this slot again. + */ + Disconnect DisconnectFunction +} + +/* Clear +\warn cannot use the slot which has been already Destructed +*/ +func (s *Slot) Destruct() { + s.Handler = nil + s.Disconnect = nil + s.Connection.disconnect = nil + s.Connection = nil +} + +func NewSlotByHandler(handler Handler) *Slot { + s := new(Slot) + s.Handler = handler + return s +} diff --git a/utils/Signal_test.go b/utils/Signal_test.go new file mode 100644 index 0000000..fabb674 --- /dev/null +++ b/utils/Signal_test.go @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2014-2022, Peking University Shenzhen Graduate School + * + * This file is part of MIN-Sync. + * See AUTHORS.md for complete list of MIN-Sync authors and contributors. + * + * MIN-Sync is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * MIN-Sync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * MIN-Sync, e.g., in COPYING.md file. If not, see . + * + + * This file incorporates work covered by the following copyright and + * permission notice: + + * The MIT License (MIT) + + * Copyright (c) 2000 Arash Partow + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package utils + +import ( + "testing" +) + +func TestZeroSlot(t *testing.T) { + s := NewSignal() + s.Emit() +} + +func TestTwoListener(t *testing.T) { + s := NewSignal() + + hit1 := 0 + hit2 := 0 + + func1 := func(...interface{}) { + hit1 += 1 + } + func2 := func(...interface{}) { + hit2 += 1 + } + + s.Connect(func1) + s.Connect(func2) + + s.Emit() + + if hit1 != 1 || hit2 != 1 { + t.Fatalf("Two listener failed.") + } +} + +func TestOneArgument(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + num := args[0].(int) + if num != 8106 { + panic("Test one argument failed.") + } + } + + s.Connect(func1) + + s.Emit(8106) + + if hit1 != 1 { + t.Fatalf("Two listener failed.") + } +} + +func TestTwoArguments(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + num := args[0].(int) + if num != 8106 { + panic("Test two arguments failed.") + } + num1 := args[1].(int) + if num1 != 8107 { + panic("Test two arguments failed.") + } + } + + s.Connect(func1) + + s.Emit(8106, 8107) + + if hit1 != 1 { + t.Fatalf("Two listener failed.") + } +} + +func TestManualDisconnect(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + } + + conn := s.Connect(func1) + + s.Emit() + if hit1 != 1 { + t.Fatalf("ManualDisconnect failed.") + } + + if !conn.IsConnected() { + t.Fatalf("ManualDisconnect failed.") + } + + conn.Disconnect() + + if conn.IsConnected() { + t.Fatalf("ManualDisconnect failed.") + } + + s.Emit() + if hit1 != 1 { + t.Fatalf("ManualDisconnect failed.") + } + + conn.Disconnect() +} + +func TestManualDisconnectDestructed(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + } + + conn := s.Connect(func1) + + s.Emit() + if hit1 != 1 { + t.Fatalf("ManualDisconnectDestructed failed.") + } + + s.Clear() + if conn.IsConnected() { + t.Fatalf("ManualDisconnectDestructed failed.") + } + s.Clear() + s.Emit() + s.Clear() + s.Emit() + if hit1 != 1 { + t.Fatalf("ManualDisconnectDestructed failed.") + } +} + +func TestConnectSingleSlot(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + } + + conn := s.ConnectSingleShot(func1) + + s.Emit() + if hit1 != 1 { + t.Fatalf("ConnectSingleSlot failed.") + } + + if conn.IsConnected() { + t.Fatalf("ConnectSingleSlot failed.") + } +} + +func TestConnectSingleShotDisconnected(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + } + + conn := s.ConnectSingleShot(func1) + + if !conn.IsConnected() { + t.Fatalf("ConnectSingleShotDisconnected failed.") + } + + conn.Disconnect() + + if conn.IsConnected() { + t.Fatalf("ConnectSingleShotDisconnected failed.") + } + + s.Emit() + if hit1 != 0 { + t.Fatalf("ConnectSingleShotDisconnected failed.") + } +} + +func TestConnectSingleShot1(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + func1 := func(args ...interface{}) { + hit1 += 1 + } + + conn := s.ConnectSingleShot(func1) + + s.Emit() + if hit1 != 1 { + t.Fatalf("ConnectSingleShot1 failed.") + } + + if conn.IsConnected() { + t.Fatalf("ConnectSingleShot1 failed.") + } + + s.Emit() + if hit1 != 1 { + t.Fatalf("ConnectSingleShot1 failed.") + } +} + +func TestConnectInHandler(t *testing.T) { + s := NewSignal() + + hit1 := 0 + hit2 := 0 + hasHandler2 := false + + func1 := func(args ...interface{}) { + hit1 += 1 + if !hasHandler2 { + s.Connect(func(i ...interface{}) { + hit2 += 1 + hasHandler2 = true + }) + } + } + + s.Connect(func1) + + s.Emit() + if hit1 != 1 || hit2 != 0 { + t.Fatalf("ConnectInHandler failed.") + } + + s.Emit() + if hit1 != 2 || hit2 != 1 { + t.Fatalf("ConnectInHandler failed.") + } +} + +func TestDisconnectSelfInHandler(t *testing.T) { + s := NewSignal() + + hit1 := 0 + + var conn *Connection + func1 := func(args ...interface{}) { + hit1 += 1 + if !conn.IsConnected() { + t.Fatalf("DisconnectSelfInHandler failed.") + } + conn.Disconnect() + if conn.IsConnected() { + t.Fatalf("DisconnectSelfInHandler failed.") + } + if s.IsEmpty() { + t.Fatalf("DisconnectSelfInHandler failed.") + } + } + + conn = s.Connect(func1) + s.Emit() + + if hit1 != 1 { + t.Fatalf("DisconnectSelfInHandler failed.") + } + + if conn.IsConnected() { + t.Fatalf("DisconnectSelfInHandler failed.") + } + + if !s.IsEmpty() { + t.Fatalf("DisconnectSelfInHandler failed.") + } + + s.Emit() + + if hit1 != 1 { + t.Fatalf("DisconnectSelfInHandler failed.") + } +} + +func TestThrowInHandler(t *testing.T) { + s := NewSignal() + + hit1 := 0 + func1 := func(args ...interface{}) { + hit1 += 1 + panic("hello") + } + + s.Connect(func1) + defer func() { + if r := recover(); r != nil { + if hit1 != 1 { + t.Fatalf("ThrowInHandler failed.") + } + } + }() + s.Emit() +}