@ -0,0 +1,201 @@ | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "{}" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright {yyyy} {name of copyright owner} | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,9 @@ | |||||
.PHONY: docs | |||||
REPO:=github.com/tendermint/go-events | |||||
docs: | |||||
@go get github.com/davecheney/godoc2md | |||||
godoc2md $(REPO) > README.md | |||||
test: | |||||
go test -v ./... |
@ -0,0 +1,175 @@ | |||||
# events | |||||
`import "github.com/tendermint/go-events"` | |||||
* [Overview](#pkg-overview) | |||||
* [Index](#pkg-index) | |||||
## <a name="pkg-overview">Overview</a> | |||||
Pub-Sub in go with event caching | |||||
## <a name="pkg-index">Index</a> | |||||
* [type EventCache](#EventCache) | |||||
* [func NewEventCache(evsw Fireable) *EventCache](#NewEventCache) | |||||
* [func (evc *EventCache) FireEvent(event string, data EventData)](#EventCache.FireEvent) | |||||
* [func (evc *EventCache) Flush()](#EventCache.Flush) | |||||
* [type EventCallback](#EventCallback) | |||||
* [type EventData](#EventData) | |||||
* [type EventSwitch](#EventSwitch) | |||||
* [func NewEventSwitch() EventSwitch](#NewEventSwitch) | |||||
* [type Eventable](#Eventable) | |||||
* [type Fireable](#Fireable) | |||||
#### <a name="pkg-files">Package files</a> | |||||
[event_cache.go](/src/github.com/tendermint/go-events/event_cache.go) [events.go](/src/github.com/tendermint/go-events/events.go) [log.go](/src/github.com/tendermint/go-events/log.go) | |||||
## <a name="EventCache">type</a> [EventCache](/src/target/event_cache.go?s=152:215#L1) | |||||
``` go | |||||
type EventCache struct { | |||||
// contains filtered or unexported fields | |||||
} | |||||
``` | |||||
An EventCache buffers events for a Fireable | |||||
All events are cached. Filtering happens on Flush | |||||
### <a name="NewEventCache">func</a> [NewEventCache](/src/target/event_cache.go?s=275:320#L5) | |||||
``` go | |||||
func NewEventCache(evsw Fireable) *EventCache | |||||
``` | |||||
Create a new EventCache with an EventSwitch as backend | |||||
### <a name="EventCache.FireEvent">func</a> (\*EventCache) [FireEvent](/src/target/event_cache.go?s=534:596#L19) | |||||
``` go | |||||
func (evc *EventCache) FireEvent(event string, data EventData) | |||||
``` | |||||
Cache an event to be fired upon finality. | |||||
### <a name="EventCache.Flush">func</a> (\*EventCache) [Flush](/src/target/event_cache.go?s=773:803#L26) | |||||
``` go | |||||
func (evc *EventCache) Flush() | |||||
``` | |||||
Fire events by running evsw.FireEvent on all cached events. Blocks. | |||||
Clears cached events | |||||
## <a name="EventCallback">type</a> [EventCallback](/src/target/events.go?s=4182:4221#L175) | |||||
``` go | |||||
type EventCallback func(data EventData) | |||||
``` | |||||
## <a name="EventData">type</a> [EventData](/src/target/events.go?s=236:287#L4) | |||||
``` go | |||||
type EventData interface { | |||||
} | |||||
``` | |||||
Generic event data can be typed and registered with tendermint/go-wire | |||||
via concrete implementation of this interface | |||||
## <a name="EventSwitch">type</a> [EventSwitch](/src/target/events.go?s=553:760#L19) | |||||
``` go | |||||
type EventSwitch interface { | |||||
Service | |||||
Fireable | |||||
AddListenerForEvent(listenerID, event string, cb EventCallback) | |||||
RemoveListenerForEvent(event string, listenerID string) | |||||
RemoveListener(listenerID string) | |||||
} | |||||
``` | |||||
### <a name="NewEventSwitch">func</a> [NewEventSwitch](/src/target/events.go?s=902:935#L36) | |||||
``` go | |||||
func NewEventSwitch() EventSwitch | |||||
``` | |||||
## <a name="Eventable">type</a> [Eventable](/src/target/events.go?s=371:433#L10) | |||||
``` go | |||||
type Eventable interface { | |||||
SetEventSwitch(evsw EventSwitch) | |||||
} | |||||
``` | |||||
reactors and other modules should export | |||||
this interface to become eventable | |||||
## <a name="Fireable">type</a> [Fireable](/src/target/events.go?s=483:551#L15) | |||||
``` go | |||||
type Fireable interface { | |||||
FireEvent(event string, data EventData) | |||||
} | |||||
``` | |||||
an event switch or cache implements fireable | |||||
- - - | |||||
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) |
@ -0,0 +1,41 @@ | |||||
package events | |||||
const ( | |||||
eventsBufferSize = 1000 | |||||
) | |||||
// An EventCache buffers events for a Fireable | |||||
// All events are cached. Filtering happens on Flush | |||||
type EventCache struct { | |||||
evsw Fireable | |||||
events []eventInfo | |||||
} | |||||
// Create a new EventCache with an EventSwitch as backend | |||||
func NewEventCache(evsw Fireable) *EventCache { | |||||
return &EventCache{ | |||||
evsw: evsw, | |||||
events: make([]eventInfo, eventsBufferSize), | |||||
} | |||||
} | |||||
// a cached event | |||||
type eventInfo struct { | |||||
event string | |||||
data EventData | |||||
} | |||||
// Cache an event to be fired upon finality. | |||||
func (evc *EventCache) FireEvent(event string, data EventData) { | |||||
// append to list | |||||
evc.events = append(evc.events, eventInfo{event, data}) | |||||
} | |||||
// Fire events by running evsw.FireEvent on all cached events. Blocks. | |||||
// Clears cached events | |||||
func (evc *EventCache) Flush() { | |||||
for _, ei := range evc.events { | |||||
evc.evsw.FireEvent(ei.event, ei.data) | |||||
} | |||||
evc.events = make([]eventInfo, eventsBufferSize) | |||||
} |
@ -0,0 +1,226 @@ | |||||
/* | |||||
Pub-Sub in go with event caching | |||||
*/ | |||||
package events | |||||
import ( | |||||
"sync" | |||||
. "github.com/tendermint/go-common" | |||||
) | |||||
// Generic event data can be typed and registered with tendermint/go-wire | |||||
// via concrete implementation of this interface | |||||
type EventData interface { | |||||
//AssertIsEventData() | |||||
} | |||||
// reactors and other modules should export | |||||
// this interface to become eventable | |||||
type Eventable interface { | |||||
SetEventSwitch(evsw EventSwitch) | |||||
} | |||||
// an event switch or cache implements fireable | |||||
type Fireable interface { | |||||
FireEvent(event string, data EventData) | |||||
} | |||||
type EventSwitch interface { | |||||
Service | |||||
Fireable | |||||
AddListenerForEvent(listenerID, event string, cb EventCallback) | |||||
RemoveListenerForEvent(event string, listenerID string) | |||||
RemoveListener(listenerID string) | |||||
} | |||||
type eventSwitch struct { | |||||
BaseService | |||||
mtx sync.RWMutex | |||||
eventCells map[string]*eventCell | |||||
listeners map[string]*eventListener | |||||
} | |||||
func NewEventSwitch() EventSwitch { | |||||
evsw := &eventSwitch{} | |||||
evsw.BaseService = *NewBaseService(log, "EventSwitch", evsw) | |||||
return evsw | |||||
} | |||||
func (evsw *eventSwitch) OnStart() error { | |||||
evsw.BaseService.OnStart() | |||||
evsw.eventCells = make(map[string]*eventCell) | |||||
evsw.listeners = make(map[string]*eventListener) | |||||
return nil | |||||
} | |||||
func (evsw *eventSwitch) OnStop() { | |||||
evsw.mtx.Lock() | |||||
defer evsw.mtx.Unlock() | |||||
evsw.BaseService.OnStop() | |||||
evsw.eventCells = nil | |||||
evsw.listeners = nil | |||||
} | |||||
func (evsw *eventSwitch) AddListenerForEvent(listenerID, event string, cb EventCallback) { | |||||
// Get/Create eventCell and listener | |||||
evsw.mtx.Lock() | |||||
eventCell := evsw.eventCells[event] | |||||
if eventCell == nil { | |||||
eventCell = newEventCell() | |||||
evsw.eventCells[event] = eventCell | |||||
} | |||||
listener := evsw.listeners[listenerID] | |||||
if listener == nil { | |||||
listener = newEventListener(listenerID) | |||||
evsw.listeners[listenerID] = listener | |||||
} | |||||
evsw.mtx.Unlock() | |||||
// Add event and listener | |||||
eventCell.AddListener(listenerID, cb) | |||||
listener.AddEvent(event) | |||||
} | |||||
func (evsw *eventSwitch) RemoveListener(listenerID string) { | |||||
// Get and remove listener | |||||
evsw.mtx.RLock() | |||||
listener := evsw.listeners[listenerID] | |||||
evsw.mtx.RUnlock() | |||||
if listener == nil { | |||||
return | |||||
} | |||||
evsw.mtx.Lock() | |||||
delete(evsw.listeners, listenerID) | |||||
evsw.mtx.Unlock() | |||||
// Remove callback for each event. | |||||
listener.SetRemoved() | |||||
for _, event := range listener.GetEvents() { | |||||
evsw.RemoveListenerForEvent(event, listenerID) | |||||
} | |||||
} | |||||
func (evsw *eventSwitch) RemoveListenerForEvent(event string, listenerID string) { | |||||
// Get eventCell | |||||
evsw.mtx.Lock() | |||||
eventCell := evsw.eventCells[event] | |||||
evsw.mtx.Unlock() | |||||
if eventCell == nil { | |||||
return | |||||
} | |||||
// Remove listenerID from eventCell | |||||
numListeners := eventCell.RemoveListener(listenerID) | |||||
// Maybe garbage collect eventCell. | |||||
if numListeners == 0 { | |||||
// Lock again and double check. | |||||
evsw.mtx.Lock() // OUTER LOCK | |||||
eventCell.mtx.Lock() // INNER LOCK | |||||
if len(eventCell.listeners) == 0 { | |||||
delete(evsw.eventCells, event) | |||||
} | |||||
eventCell.mtx.Unlock() // INNER LOCK | |||||
evsw.mtx.Unlock() // OUTER LOCK | |||||
} | |||||
} | |||||
func (evsw *eventSwitch) FireEvent(event string, data EventData) { | |||||
// Get the eventCell | |||||
evsw.mtx.RLock() | |||||
eventCell := evsw.eventCells[event] | |||||
evsw.mtx.RUnlock() | |||||
if eventCell == nil { | |||||
return | |||||
} | |||||
// Fire event for all listeners in eventCell | |||||
eventCell.FireEvent(data) | |||||
} | |||||
//----------------------------------------------------------------------------- | |||||
// eventCell handles keeping track of listener callbacks for a given event. | |||||
type eventCell struct { | |||||
mtx sync.RWMutex | |||||
listeners map[string]EventCallback | |||||
} | |||||
func newEventCell() *eventCell { | |||||
return &eventCell{ | |||||
listeners: make(map[string]EventCallback), | |||||
} | |||||
} | |||||
func (cell *eventCell) AddListener(listenerID string, cb EventCallback) { | |||||
cell.mtx.Lock() | |||||
cell.listeners[listenerID] = cb | |||||
cell.mtx.Unlock() | |||||
} | |||||
func (cell *eventCell) RemoveListener(listenerID string) int { | |||||
cell.mtx.Lock() | |||||
delete(cell.listeners, listenerID) | |||||
numListeners := len(cell.listeners) | |||||
cell.mtx.Unlock() | |||||
return numListeners | |||||
} | |||||
func (cell *eventCell) FireEvent(data EventData) { | |||||
cell.mtx.RLock() | |||||
for _, listener := range cell.listeners { | |||||
listener(data) | |||||
} | |||||
cell.mtx.RUnlock() | |||||
} | |||||
//----------------------------------------------------------------------------- | |||||
type EventCallback func(data EventData) | |||||
type eventListener struct { | |||||
id string | |||||
mtx sync.RWMutex | |||||
removed bool | |||||
events []string | |||||
} | |||||
func newEventListener(id string) *eventListener { | |||||
return &eventListener{ | |||||
id: id, | |||||
removed: false, | |||||
events: nil, | |||||
} | |||||
} | |||||
func (evl *eventListener) AddEvent(event string) { | |||||
evl.mtx.Lock() | |||||
defer evl.mtx.Unlock() | |||||
if evl.removed { | |||||
return | |||||
} | |||||
evl.events = append(evl.events, event) | |||||
} | |||||
func (evl *eventListener) GetEvents() []string { | |||||
evl.mtx.RLock() | |||||
defer evl.mtx.RUnlock() | |||||
events := make([]string, len(evl.events)) | |||||
copy(events, evl.events) | |||||
return events | |||||
} | |||||
func (evl *eventListener) SetRemoved() { | |||||
evl.mtx.Lock() | |||||
defer evl.mtx.Unlock() | |||||
evl.removed = true | |||||
} |
@ -0,0 +1,381 @@ | |||||
package events | |||||
import ( | |||||
"fmt" | |||||
"math/rand" | |||||
"testing" | |||||
"time" | |||||
"github.com/stretchr/testify/assert" | |||||
) | |||||
// TestAddListenerForEventFireOnce sets up an EventSwitch, subscribes a single | |||||
// listener to an event, and sends a string "data". | |||||
func TestAddListenerForEventFireOnce(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
messages := make(chan EventData) | |||||
evsw.AddListenerForEvent("listener", "event", | |||||
func(data EventData) { | |||||
messages <- data | |||||
}) | |||||
go evsw.FireEvent("event", "data") | |||||
received := <-messages | |||||
if received != "data" { | |||||
t.Errorf("Message received does not match: %v", received) | |||||
} | |||||
} | |||||
// TestAddListenerForEventFireMany sets up an EventSwitch, subscribes a single | |||||
// listener to an event, and sends a thousand integers. | |||||
func TestAddListenerForEventFireMany(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
doneSum := make(chan uint64) | |||||
doneSending := make(chan uint64) | |||||
numbers := make(chan uint64, 4) | |||||
// subscribe one listener for one event | |||||
evsw.AddListenerForEvent("listener", "event", | |||||
func(data EventData) { | |||||
numbers <- data.(uint64) | |||||
}) | |||||
// collect received events | |||||
go sumReceivedNumbers(numbers, doneSum) | |||||
// go fire events | |||||
go fireEvents(evsw, "event", doneSending, uint64(1)) | |||||
checkSum := <-doneSending | |||||
close(numbers) | |||||
eventSum := <-doneSum | |||||
if checkSum != eventSum { | |||||
t.Errorf("Not all messages sent were received.\n") | |||||
} | |||||
} | |||||
// TestAddListenerForDifferentEvents sets up an EventSwitch, subscribes a single | |||||
// listener to three different events and sends a thousand integers for each | |||||
// of the three events. | |||||
func TestAddListenerForDifferentEvents(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
doneSum := make(chan uint64) | |||||
doneSending1 := make(chan uint64) | |||||
doneSending2 := make(chan uint64) | |||||
doneSending3 := make(chan uint64) | |||||
numbers := make(chan uint64, 4) | |||||
// subscribe one listener to three events | |||||
evsw.AddListenerForEvent("listener", "event1", | |||||
func(data EventData) { | |||||
numbers <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener", "event2", | |||||
func(data EventData) { | |||||
numbers <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener", "event3", | |||||
func(data EventData) { | |||||
numbers <- data.(uint64) | |||||
}) | |||||
// collect received events | |||||
go sumReceivedNumbers(numbers, doneSum) | |||||
// go fire events | |||||
go fireEvents(evsw, "event1", doneSending1, uint64(1)) | |||||
go fireEvents(evsw, "event2", doneSending2, uint64(1)) | |||||
go fireEvents(evsw, "event3", doneSending3, uint64(1)) | |||||
var checkSum uint64 = 0 | |||||
checkSum += <-doneSending1 | |||||
checkSum += <-doneSending2 | |||||
checkSum += <-doneSending3 | |||||
close(numbers) | |||||
eventSum := <-doneSum | |||||
if checkSum != eventSum { | |||||
t.Errorf("Not all messages sent were received.\n") | |||||
} | |||||
} | |||||
// TestAddDifferentListenerForDifferentEvents sets up an EventSwitch, | |||||
// subscribes a first listener to three events, and subscribes a second | |||||
// listener to two of those three events, and then sends a thousand integers | |||||
// for each of the three events. | |||||
func TestAddDifferentListenerForDifferentEvents(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
doneSum1 := make(chan uint64) | |||||
doneSum2 := make(chan uint64) | |||||
doneSending1 := make(chan uint64) | |||||
doneSending2 := make(chan uint64) | |||||
doneSending3 := make(chan uint64) | |||||
numbers1 := make(chan uint64, 4) | |||||
numbers2 := make(chan uint64, 4) | |||||
// subscribe two listener to three events | |||||
evsw.AddListenerForEvent("listener1", "event1", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener1", "event2", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener1", "event3", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener2", "event2", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener2", "event3", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
// collect received events for listener1 | |||||
go sumReceivedNumbers(numbers1, doneSum1) | |||||
// collect received events for listener2 | |||||
go sumReceivedNumbers(numbers2, doneSum2) | |||||
// go fire events | |||||
go fireEvents(evsw, "event1", doneSending1, uint64(1)) | |||||
go fireEvents(evsw, "event2", doneSending2, uint64(1001)) | |||||
go fireEvents(evsw, "event3", doneSending3, uint64(2001)) | |||||
checkSumEvent1 := <-doneSending1 | |||||
checkSumEvent2 := <-doneSending2 | |||||
checkSumEvent3 := <-doneSending3 | |||||
checkSum1 := checkSumEvent1 + checkSumEvent2 + checkSumEvent3 | |||||
checkSum2 := checkSumEvent2 + checkSumEvent3 | |||||
close(numbers1) | |||||
close(numbers2) | |||||
eventSum1 := <-doneSum1 | |||||
eventSum2 := <-doneSum2 | |||||
if checkSum1 != eventSum1 || | |||||
checkSum2 != eventSum2 { | |||||
t.Errorf("Not all messages sent were received for different listeners to different events.\n") | |||||
} | |||||
} | |||||
// TestAddAndRemoveListener sets up an EventSwitch, subscribes a listener to | |||||
// two events, fires a thousand integers for the first event, then unsubscribes | |||||
// the listener and fires a thousand integers for the second event. | |||||
func TestAddAndRemoveListener(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
doneSum1 := make(chan uint64) | |||||
doneSum2 := make(chan uint64) | |||||
doneSending1 := make(chan uint64) | |||||
doneSending2 := make(chan uint64) | |||||
numbers1 := make(chan uint64, 4) | |||||
numbers2 := make(chan uint64, 4) | |||||
// subscribe two listener to three events | |||||
evsw.AddListenerForEvent("listener", "event1", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener", "event2", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
// collect received events for event1 | |||||
go sumReceivedNumbers(numbers1, doneSum1) | |||||
// collect received events for event2 | |||||
go sumReceivedNumbers(numbers2, doneSum2) | |||||
// go fire events | |||||
go fireEvents(evsw, "event1", doneSending1, uint64(1)) | |||||
checkSumEvent1 := <-doneSending1 | |||||
// after sending all event1, unsubscribe for all events | |||||
evsw.RemoveListener("listener") | |||||
go fireEvents(evsw, "event2", doneSending2, uint64(1001)) | |||||
checkSumEvent2 := <-doneSending2 | |||||
close(numbers1) | |||||
close(numbers2) | |||||
eventSum1 := <-doneSum1 | |||||
eventSum2 := <-doneSum2 | |||||
if checkSumEvent1 != eventSum1 || | |||||
// correct value asserted by preceding tests, suffices to be non-zero | |||||
checkSumEvent2 == uint64(0) || | |||||
eventSum2 != uint64(0) { | |||||
t.Errorf("Not all messages sent were received or unsubscription did not register.\n") | |||||
} | |||||
} | |||||
// TestRemoveListener does basic tests on adding and removing | |||||
func TestRemoveListener(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
count := 10 | |||||
sum1, sum2 := 0, 0 | |||||
// add some listeners and make sure they work | |||||
evsw.AddListenerForEvent("listener", "event1", | |||||
func(data EventData) { | |||||
sum1 += 1 | |||||
}) | |||||
evsw.AddListenerForEvent("listener", "event2", | |||||
func(data EventData) { | |||||
sum2 += 1 | |||||
}) | |||||
for i := 0; i < count; i++ { | |||||
evsw.FireEvent("event1", true) | |||||
evsw.FireEvent("event2", true) | |||||
} | |||||
assert.Equal(t, count, sum1) | |||||
assert.Equal(t, count, sum2) | |||||
// remove one by event and make sure it is gone | |||||
evsw.RemoveListenerForEvent("event2", "listener") | |||||
for i := 0; i < count; i++ { | |||||
evsw.FireEvent("event1", true) | |||||
evsw.FireEvent("event2", true) | |||||
} | |||||
assert.Equal(t, count*2, sum1) | |||||
assert.Equal(t, count, sum2) | |||||
// remove the listener entirely and make sure both gone | |||||
evsw.RemoveListener("listener") | |||||
for i := 0; i < count; i++ { | |||||
evsw.FireEvent("event1", true) | |||||
evsw.FireEvent("event2", true) | |||||
} | |||||
assert.Equal(t, count*2, sum1) | |||||
assert.Equal(t, count, sum2) | |||||
} | |||||
// TestAddAndRemoveListenersAsync sets up an EventSwitch, subscribes two | |||||
// listeners to three events, and fires a thousand integers for each event. | |||||
// These two listeners serve as the baseline validation while other listeners | |||||
// are randomly subscribed and unsubscribed. | |||||
// More precisely it randomly subscribes new listeners (different from the first | |||||
// two listeners) to one of these three events. At the same time it starts | |||||
// randomly unsubscribing these additional listeners from all events they are | |||||
// at that point subscribed to. | |||||
// NOTE: it is important to run this test with race conditions tracking on, | |||||
// `go test -race`, to examine for possible race conditions. | |||||
func TestRemoveListenersAsync(t *testing.T) { | |||||
evsw := NewEventSwitch() | |||||
started, err := evsw.Start() | |||||
if started == false || err != nil { | |||||
t.Errorf("Failed to start EventSwitch, error: %v", err) | |||||
} | |||||
doneSum1 := make(chan uint64) | |||||
doneSum2 := make(chan uint64) | |||||
doneSending1 := make(chan uint64) | |||||
doneSending2 := make(chan uint64) | |||||
doneSending3 := make(chan uint64) | |||||
numbers1 := make(chan uint64, 4) | |||||
numbers2 := make(chan uint64, 4) | |||||
// subscribe two listener to three events | |||||
evsw.AddListenerForEvent("listener1", "event1", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener1", "event2", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener1", "event3", | |||||
func(data EventData) { | |||||
numbers1 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener2", "event1", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener2", "event2", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
evsw.AddListenerForEvent("listener2", "event3", | |||||
func(data EventData) { | |||||
numbers2 <- data.(uint64) | |||||
}) | |||||
// collect received events for event1 | |||||
go sumReceivedNumbers(numbers1, doneSum1) | |||||
// collect received events for event2 | |||||
go sumReceivedNumbers(numbers2, doneSum2) | |||||
addListenersStress := func() { | |||||
s1 := rand.NewSource(time.Now().UnixNano()) | |||||
r1 := rand.New(s1) | |||||
for k := uint16(0); k < 400; k++ { | |||||
listenerNumber := r1.Intn(100) + 3 | |||||
eventNumber := r1.Intn(3) + 1 | |||||
go evsw.AddListenerForEvent(fmt.Sprintf("listener%v", listenerNumber), | |||||
fmt.Sprintf("event%v", eventNumber), | |||||
func(_ EventData) {}) | |||||
} | |||||
} | |||||
removeListenersStress := func() { | |||||
s2 := rand.NewSource(time.Now().UnixNano()) | |||||
r2 := rand.New(s2) | |||||
for k := uint16(0); k < 80; k++ { | |||||
listenerNumber := r2.Intn(100) + 3 | |||||
go evsw.RemoveListener(fmt.Sprintf("listener%v", listenerNumber)) | |||||
} | |||||
} | |||||
addListenersStress() | |||||
// go fire events | |||||
go fireEvents(evsw, "event1", doneSending1, uint64(1)) | |||||
removeListenersStress() | |||||
go fireEvents(evsw, "event2", doneSending2, uint64(1001)) | |||||
go fireEvents(evsw, "event3", doneSending3, uint64(2001)) | |||||
checkSumEvent1 := <-doneSending1 | |||||
checkSumEvent2 := <-doneSending2 | |||||
checkSumEvent3 := <-doneSending3 | |||||
checkSum := checkSumEvent1 + checkSumEvent2 + checkSumEvent3 | |||||
close(numbers1) | |||||
close(numbers2) | |||||
eventSum1 := <-doneSum1 | |||||
eventSum2 := <-doneSum2 | |||||
if checkSum != eventSum1 || | |||||
checkSum != eventSum2 { | |||||
t.Errorf("Not all messages sent were received.\n") | |||||
} | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
// Helper functions | |||||
// sumReceivedNumbers takes two channels and adds all numbers received | |||||
// until the receiving channel `numbers` is closed; it then sends the sum | |||||
// on `doneSum` and closes that channel. Expected to be run in a go-routine. | |||||
func sumReceivedNumbers(numbers, doneSum chan uint64) { | |||||
var sum uint64 = 0 | |||||
for { | |||||
j, more := <-numbers | |||||
sum += j | |||||
if !more { | |||||
doneSum <- sum | |||||
close(doneSum) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
// fireEvents takes an EventSwitch and fires a thousand integers under | |||||
// a given `event` with the integers mootonically increasing from `offset` | |||||
// to `offset` + 999. It additionally returns the addition of all integers | |||||
// sent on `doneChan` for assertion that all events have been sent, and enabling | |||||
// the test to assert all events have also been received. | |||||
func fireEvents(evsw EventSwitch, event string, doneChan chan uint64, | |||||
offset uint64) { | |||||
var sentSum uint64 = 0 | |||||
for i := offset; i <= offset+uint64(999); i++ { | |||||
sentSum += i | |||||
evsw.FireEvent(event, i) | |||||
} | |||||
doneChan <- sentSum | |||||
close(doneChan) | |||||
return | |||||
} |
@ -0,0 +1,7 @@ | |||||
package events | |||||
import ( | |||||
"github.com/tendermint/go-logger" | |||||
) | |||||
var log = logger.New("module", "events") |