You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

426 lines
12 KiB

pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
abci: Refactor tagging events using list of lists (#3643) ## PR This PR introduces a fundamental breaking change to the structure of ABCI response and tx tags and the way they're processed. Namely, the SDK can support more complex and aggregated events for distribution and slashing. In addition, block responses can include duplicate keys in events. Implement new Event type. An event has a type and a list of KV pairs (ie. list-of-lists). Typical events may look like: "rewards": [{"amount": "5000uatom", "validator": "...", "recipient": "..."}] "sender": [{"address": "...", "balance": "100uatom"}] The events are indexed by {even.type}.{even.attribute[i].key}/.... In this case a client would subscribe or query for rewards.recipient='...' ABCI response types and related types now include Events []Event instead of Tags []cmn.KVPair. PubSub logic now publishes/matches against map[string][]string instead of map[string]string to support duplicate keys in response events (from #1385). A match is successful if the value is found in the slice of strings. closes: #1859 closes: #2905 ## Commits: * Implement Event ABCI type and updates responses to use events * Update messages_test.go * Update kvstore.go * Update event_bus.go * Update subscription.go * Update pubsub.go * Update kvstore.go * Update query logic to handle slice of strings in events * Update Empty#Matches and unit tests * Update pubsub logic * Update EventBus#Publish * Update kv tx indexer * Update godocs * Update ResultEvent to use slice of strings; update RPC * Update more tests * Update abci.md * Check for key in validateAndStringifyEvents * Fix KV indexer to skip empty keys * Fix linting errors * Update CHANGELOG_PENDING.md * Update docs/spec/abci/abci.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * Update abci/types/types.proto Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update docs/spec/abci/abci.md Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update libs/pubsub/query/query.go Co-Authored-By: Ethan Buchman <ethan@coinculture.info> * Update match function to match if ANY value matches * Implement TestSubscribeDuplicateKeys * Update TestMatches to include multi-key test cases * Update events.go * Update Query interface godoc * Update match godoc * Add godoc for matchValue * DRY-up tx indexing * Return error from PublishWithEvents in EventBus#Publish * Update PublishEventNewBlockHeader to return an error * Fix build * Update events doc in ABCI * Update ABCI events godoc * Implement TestEventBusPublishEventTxDuplicateKeys * Update TestSubscribeDuplicateKeys to be table-driven * Remove mod file * Remove markdown from events godoc * Implement TestTxSearchDeprecatedIndexing test
5 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
pubsub 2.0 (#3227) * green pubsub tests :OK: * get rid of clientToQueryMap * Subscribe and SubscribeUnbuffered * start adapting other pkgs to new pubsub * nope * rename MsgAndTags to Message * remove TagMap it does not bring any additional benefits * bring back EventSubscriber * fix test * fix data race in TestStartNextHeightCorrectly ``` Write at 0x00c0001c7418 by goroutine 796: github.com/tendermint/tendermint/consensus.TestStartNextHeightCorrectly() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:1296 +0xad testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Previous read at 0x00c0001c7418 by goroutine 858: github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1631 +0x1366 github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote() /go/src/github.com/tendermint/tendermint/consensus/state.go:1476 +0x8f github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg() /go/src/github.com/tendermint/tendermint/consensus/state.go:667 +0xa1e github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine() /go/src/github.com/tendermint/tendermint/consensus/state.go:628 +0x794 Goroutine 796 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 testing.runTests.func1() /usr/local/go/src/testing/testing.go:1119 +0xa8 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 testing.runTests() /usr/local/go/src/testing/testing.go:1117 +0x4ee testing.(*M).Run() /usr/local/go/src/testing/testing.go:1034 +0x2ee main.main() _testmain.go:214 +0x332 Goroutine 858 (running) created at: github.com/tendermint/tendermint/consensus.(*ConsensusState).startRoutines() /go/src/github.com/tendermint/tendermint/consensus/state.go:334 +0x221 github.com/tendermint/tendermint/consensus.startTestRound() /go/src/github.com/tendermint/tendermint/consensus/common_test.go:122 +0x63 github.com/tendermint/tendermint/consensus.TestStateFullRound1() /go/src/github.com/tendermint/tendermint/consensus/state_test.go:255 +0x397 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ``` * fixes after my own review * fix formatting * wait 100ms before kicking a subscriber out + a test for indexer_service * fixes after my second review * no timeout * add changelog entries * fix merge conflicts * fix typos after Thane's review Co-Authored-By: melekes <anton.kalyaev@gmail.com> * reformat code * rewrite indexer service in the attempt to fix failing test https://github.com/tendermint/tendermint/pull/3227/#issuecomment-462316527 * Revert "rewrite indexer service in the attempt to fix failing test" This reverts commit 0d9107a098230de7138abb1c201877c246e89ed1. * another attempt to fix indexer * fixes after Ethan's review * use unbuffered channel when indexing transactions Refs https://github.com/tendermint/tendermint/pull/3227#discussion_r258786716 * add a comment for EventBus#SubscribeUnbuffered * format code
6 years ago
libs/pubsub: relax tx querying (#4070) Some linting/cleanup missed from the initial events refactor Don't panic; instead, return false, error when matching breaks unexpectedly Strip non-numeric chars from values when attempting to match against query values Have the server log during send upon error * cleanup/lint Query#Conditions and do not panic * cleanup/lint Query#Matches and do not panic * cleanup/lint matchValue and do not panic * rever to panic in Query#Conditions * linting * strip alpha chars when attempting to match * add pending log entries * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * build: update variable names * update matchValue to return an error * update Query#Matches to return an error * update TestMatches * log error in send * Fix tests * Fix TestEmptyQueryMatchesAnything * fix linting * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/pubsub.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * add missing errors pkg import * update Query#Conditions to return an error * update query pkg unit tests * update TxIndex#Search * update pending changelog
5 years ago
libs/pubsub: relax tx querying (#4070) Some linting/cleanup missed from the initial events refactor Don't panic; instead, return false, error when matching breaks unexpectedly Strip non-numeric chars from values when attempting to match against query values Have the server log during send upon error * cleanup/lint Query#Conditions and do not panic * cleanup/lint Query#Matches and do not panic * cleanup/lint matchValue and do not panic * rever to panic in Query#Conditions * linting * strip alpha chars when attempting to match * add pending log entries * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * build: update variable names * update matchValue to return an error * update Query#Matches to return an error * update TestMatches * log error in send * Fix tests * Fix TestEmptyQueryMatchesAnything * fix linting * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/query/query.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * Update libs/pubsub/pubsub.go Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * add missing errors pkg import * update Query#Conditions to return an error * update query pkg unit tests * update TxIndex#Search * update pending changelog
5 years ago
  1. // Package pubsub implements an event dispatching server with a single publisher
  2. // and multiple subscriber clients. Multiple goroutines can safely publish to a
  3. // single Server instance.
  4. //
  5. // Clients register subscriptions with a query to select which messages they
  6. // wish to receive. When messages are published, they are broadcast to all
  7. // clients whose subscription query matches that message. Queries are
  8. // constructed using the github.com/tendermint/tendermint/internal/pubsub/query
  9. // package.
  10. //
  11. // Example:
  12. //
  13. // q, err := query.New(`account.name='John'`)
  14. // if err != nil {
  15. // return err
  16. // }
  17. // sub, err := pubsub.SubscribeWithArgs(ctx, pubsub.SubscribeArgs{
  18. // ClientID: "johns-transactions",
  19. // Query: q,
  20. // })
  21. // if err != nil {
  22. // return err
  23. // }
  24. //
  25. // for {
  26. // next, err := sub.Next(ctx)
  27. // if err == pubsub.ErrTerminated {
  28. // return err // terminated by publisher
  29. // } else if err != nil {
  30. // return err // timed out, client unsubscribed, etc.
  31. // }
  32. // process(next)
  33. // }
  34. //
  35. package pubsub
  36. import (
  37. "context"
  38. "errors"
  39. "fmt"
  40. "sync"
  41. abci "github.com/tendermint/tendermint/abci/types"
  42. "github.com/tendermint/tendermint/internal/pubsub/query"
  43. "github.com/tendermint/tendermint/libs/log"
  44. "github.com/tendermint/tendermint/libs/service"
  45. "github.com/tendermint/tendermint/types"
  46. )
  47. var (
  48. // ErrSubscriptionNotFound is returned when a client tries to unsubscribe
  49. // from not existing subscription.
  50. ErrSubscriptionNotFound = errors.New("subscription not found")
  51. // ErrAlreadySubscribed is returned when a client tries to subscribe twice or
  52. // more using the same query.
  53. ErrAlreadySubscribed = errors.New("already subscribed")
  54. // ErrServerStopped is returned when attempting to publish or subscribe to a
  55. // server that has been stopped.
  56. ErrServerStopped = errors.New("pubsub server is stopped")
  57. )
  58. // SubscribeArgs are the parameters to create a new subscription.
  59. type SubscribeArgs struct {
  60. ClientID string // Client ID
  61. Query *query.Query // filter query for events (required)
  62. Limit int // subscription queue capacity limit (0 means 1)
  63. Quota int // subscription queue soft quota (0 uses Limit)
  64. }
  65. // UnsubscribeArgs are the parameters to remove a subscription.
  66. // The subscriber ID must be populated, and at least one of the client ID or
  67. // the registered query.
  68. type UnsubscribeArgs struct {
  69. Subscriber string // subscriber ID chosen by the client (required)
  70. ID string // subscription ID (assigned by the server)
  71. Query *query.Query // the query registered with the subscription
  72. }
  73. // Validate returns nil if args are valid to identify a subscription to remove.
  74. // Otherwise, it reports an error.
  75. func (args UnsubscribeArgs) Validate() error {
  76. if args.Subscriber == "" {
  77. return errors.New("must specify a subscriber")
  78. }
  79. return nil
  80. }
  81. // Server allows clients to subscribe/unsubscribe for messages, publishing
  82. // messages with or without events, and manages internal state.
  83. type Server struct {
  84. service.BaseService
  85. logger log.Logger
  86. queue chan item
  87. done <-chan struct{} // closed when server should exit
  88. pubs sync.RWMutex // excl: shutdown; shared: active publisher
  89. exited chan struct{} // server exited
  90. // All subscriptions currently known.
  91. // Lock exclusive to add, remove, or cancel subscriptions.
  92. // Lock shared to look up or publish to subscriptions.
  93. subs struct {
  94. sync.RWMutex
  95. index *subIndex
  96. // This function is called synchronously with each message published
  97. // before it is delivered to any other subscriber. This allows an index
  98. // to be persisted before any subscribers see the messages.
  99. observe func(Message) error
  100. }
  101. // TODO(creachadair): Rework the options so that this does not need to live
  102. // as a field. It is not otherwise needed.
  103. queueCap int
  104. }
  105. // Option sets a parameter for the server.
  106. type Option func(*Server)
  107. // NewServer returns a new server. See the commentary on the Option functions
  108. // for a detailed description of how to configure buffering. If no options are
  109. // provided, the resulting server's queue is unbuffered.
  110. func NewServer(logger log.Logger, options ...Option) *Server {
  111. s := &Server{logger: logger}
  112. s.BaseService = *service.NewBaseService(logger, "PubSub", s)
  113. for _, opt := range options {
  114. opt(s)
  115. }
  116. // The queue receives items to be published.
  117. s.queue = make(chan item, s.queueCap)
  118. // The index tracks subscriptions by ID and query terms.
  119. s.subs.index = newSubIndex()
  120. return s
  121. }
  122. // BufferCapacity allows you to specify capacity for publisher's queue. This
  123. // is the number of messages that can be published without blocking. If no
  124. // buffer is specified, publishing is synchronous with delivery. This function
  125. // will panic if cap < 0.
  126. func BufferCapacity(cap int) Option {
  127. if cap < 0 {
  128. panic("negative buffer capacity")
  129. }
  130. return func(s *Server) { s.queueCap = cap }
  131. }
  132. // BufferCapacity returns capacity of the publication queue.
  133. func (s *Server) BufferCapacity() int { return cap(s.queue) }
  134. // Observe registers an observer function that will be called synchronously
  135. // with each published message matching any of the given queries, prior to it
  136. // being forwarded to any subscriber. If no queries are specified, all
  137. // messages will be observed. An error is reported if an observer is already
  138. // registered.
  139. func (s *Server) Observe(ctx context.Context, observe func(Message) error, queries ...*query.Query) error {
  140. s.subs.Lock()
  141. defer s.subs.Unlock()
  142. if observe == nil {
  143. return errors.New("observe callback is nil")
  144. } else if s.subs.observe != nil {
  145. return errors.New("an observer is already registered")
  146. }
  147. // Compile the message filter.
  148. var matches func(Message) bool
  149. if len(queries) == 0 {
  150. matches = func(Message) bool { return true }
  151. } else {
  152. matches = func(msg Message) bool {
  153. for _, q := range queries {
  154. if q.Matches(msg.events) {
  155. return true
  156. }
  157. }
  158. return false
  159. }
  160. }
  161. s.subs.observe = func(msg Message) error {
  162. if matches(msg) {
  163. return observe(msg)
  164. }
  165. return nil // nothing to do for this message
  166. }
  167. return nil
  168. }
  169. // SubscribeWithArgs creates a subscription for the given arguments. It is an
  170. // error if the query is nil, a subscription already exists for the specified
  171. // client ID and query, or if the capacity arguments are invalid.
  172. func (s *Server) SubscribeWithArgs(ctx context.Context, args SubscribeArgs) (*Subscription, error) {
  173. s.subs.Lock()
  174. defer s.subs.Unlock()
  175. if s.subs.index == nil {
  176. return nil, ErrServerStopped
  177. } else if s.subs.index.contains(args.ClientID, args.Query.String()) {
  178. return nil, ErrAlreadySubscribed
  179. }
  180. if args.Limit == 0 {
  181. args.Limit = 1
  182. }
  183. sub, err := newSubscription(args.Quota, args.Limit)
  184. if err != nil {
  185. return nil, err
  186. }
  187. s.subs.index.add(&subInfo{
  188. clientID: args.ClientID,
  189. query: args.Query,
  190. subID: sub.id,
  191. sub: sub,
  192. })
  193. return sub, nil
  194. }
  195. // Unsubscribe removes the subscription for the given client and/or query. It
  196. // returns ErrSubscriptionNotFound if no such subscription exists.
  197. func (s *Server) Unsubscribe(ctx context.Context, args UnsubscribeArgs) error {
  198. if err := args.Validate(); err != nil {
  199. return err
  200. }
  201. s.subs.Lock()
  202. defer s.subs.Unlock()
  203. if s.subs.index == nil {
  204. return ErrServerStopped
  205. }
  206. // TODO(creachadair): Do we need to support unsubscription for an "empty"
  207. // query? I believe that case is not possible by the Query grammar, but we
  208. // should make sure.
  209. //
  210. // Revisit this logic once we are able to remove indexing by query.
  211. var evict subInfoSet
  212. if args.Subscriber != "" {
  213. evict = s.subs.index.findClientID(args.Subscriber)
  214. if args.Query != nil {
  215. evict = evict.withQuery(args.Query.String())
  216. }
  217. } else {
  218. evict = s.subs.index.findQuery(args.Query.String())
  219. }
  220. if len(evict) == 0 {
  221. return ErrSubscriptionNotFound
  222. }
  223. s.removeSubs(evict, ErrUnsubscribed)
  224. return nil
  225. }
  226. // UnsubscribeAll removes all subscriptions for the given client ID.
  227. // It returns ErrSubscriptionNotFound if no subscriptions exist for that client.
  228. func (s *Server) UnsubscribeAll(ctx context.Context, clientID string) error {
  229. s.subs.Lock()
  230. defer s.subs.Unlock()
  231. if s.subs.index == nil {
  232. return ErrServerStopped
  233. }
  234. evict := s.subs.index.findClientID(clientID)
  235. if len(evict) == 0 {
  236. return ErrSubscriptionNotFound
  237. }
  238. s.removeSubs(evict, ErrUnsubscribed)
  239. return nil
  240. }
  241. // NumClients returns the number of clients.
  242. func (s *Server) NumClients() int {
  243. s.subs.RLock()
  244. defer s.subs.RUnlock()
  245. return len(s.subs.index.byClient)
  246. }
  247. // NumClientSubscriptions returns the number of subscriptions the client has.
  248. func (s *Server) NumClientSubscriptions(clientID string) int {
  249. s.subs.RLock()
  250. defer s.subs.RUnlock()
  251. return len(s.subs.index.findClientID(clientID))
  252. }
  253. // Publish publishes the given message. An error will be returned to the caller
  254. // if the context is canceled.
  255. func (s *Server) Publish(ctx context.Context, msg types.EventData) error {
  256. return s.publish(ctx, msg, []abci.Event{})
  257. }
  258. // PublishWithEvents publishes the given message with the set of events. The set
  259. // is matched with clients queries. If there is a match, the message is sent to
  260. // the client.
  261. func (s *Server) PublishWithEvents(ctx context.Context, msg types.EventData, events []abci.Event) error {
  262. return s.publish(ctx, msg, events)
  263. }
  264. // OnStop implements part of the Service interface. It is a no-op.
  265. func (s *Server) OnStop() {}
  266. // Wait implements Service.Wait by blocking until the server has exited, then
  267. // yielding to the base service wait.
  268. func (s *Server) Wait() { <-s.exited; s.BaseService.Wait() }
  269. // OnStart implements Service.OnStart by starting the server.
  270. func (s *Server) OnStart(ctx context.Context) error { s.run(ctx); return nil }
  271. // OnReset implements Service.OnReset. It has no effect for this service.
  272. func (s *Server) OnReset() error { return nil }
  273. func (s *Server) publish(ctx context.Context, data types.EventData, events []abci.Event) error {
  274. s.pubs.RLock()
  275. defer s.pubs.RUnlock()
  276. select {
  277. case <-s.done:
  278. return ErrServerStopped
  279. case <-ctx.Done():
  280. return ctx.Err()
  281. case s.queue <- item{
  282. Data: data,
  283. Events: events,
  284. }:
  285. return nil
  286. }
  287. }
  288. func (s *Server) run(ctx context.Context) {
  289. // The server runs until ctx is canceled.
  290. s.done = ctx.Done()
  291. queue := s.queue
  292. // Shutdown monitor: When the context ends, wait for any active publish
  293. // calls to exit, then close the queue to signal the sender to exit.
  294. go func() {
  295. <-ctx.Done()
  296. s.pubs.Lock()
  297. defer s.pubs.Unlock()
  298. close(s.queue)
  299. s.queue = nil
  300. }()
  301. s.exited = make(chan struct{})
  302. go func() {
  303. defer close(s.exited)
  304. // Sender: Service the queue and forward messages to subscribers.
  305. for it := range queue {
  306. if err := s.send(it.Data, it.Events); err != nil {
  307. s.logger.Error("error sending event", "err", err)
  308. }
  309. }
  310. // Terminate all subscribers before exit.
  311. s.subs.Lock()
  312. defer s.subs.Unlock()
  313. for si := range s.subs.index.all {
  314. si.sub.stop(ErrTerminated)
  315. }
  316. s.subs.index = nil
  317. }()
  318. }
  319. // removeSubs cancels and removes all the subscriptions in evict with the given
  320. // error. The caller must hold the s.subs lock.
  321. func (s *Server) removeSubs(evict subInfoSet, reason error) {
  322. for si := range evict {
  323. si.sub.stop(reason)
  324. }
  325. s.subs.index.removeAll(evict)
  326. }
  327. // send delivers the given message to all matching subscribers. An error in
  328. // query matching stops transmission and is returned.
  329. func (s *Server) send(data types.EventData, events []abci.Event) error {
  330. // At exit, evict any subscriptions that were too slow.
  331. evict := make(subInfoSet)
  332. defer func() {
  333. if len(evict) != 0 {
  334. s.subs.Lock()
  335. defer s.subs.Unlock()
  336. s.removeSubs(evict, ErrTerminated)
  337. }
  338. }()
  339. // N.B. Order is important here. We must acquire and defer the lock release
  340. // AFTER deferring the eviction cleanup: The cleanup must happen after the
  341. // reader lock has released, or it will deadlock.
  342. s.subs.RLock()
  343. defer s.subs.RUnlock()
  344. // If an observer is defined, give it control of the message before
  345. // attempting to deliver it to any matching subscribers. If the observer
  346. // fails, the message will not be forwarded.
  347. if s.subs.observe != nil {
  348. err := s.subs.observe(Message{
  349. data: data,
  350. events: events,
  351. })
  352. if err != nil {
  353. return fmt.Errorf("observer failed on message: %w", err)
  354. }
  355. }
  356. for si := range s.subs.index.all {
  357. if !si.query.Matches(events) {
  358. continue
  359. }
  360. // Publish the events to the subscriber's queue. If this fails, e.g.,
  361. // because the queue is over capacity or out of quota, evict the
  362. // subscription from the index.
  363. if err := si.sub.publish(Message{
  364. subID: si.sub.id,
  365. data: data,
  366. events: events,
  367. }); err != nil {
  368. evict.add(si)
  369. }
  370. }
  371. return nil
  372. }