From 6f43730169afebb6bdb5ae1b322356056dc2568c Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 3 Jul 2025 12:33:01 +0800 Subject: [PATCH 01/44] Implement Base Topics Functionality --- .github/workflows/build-topics.yaml | 83 + Makefile | 33 +- coherence/common.go | 43 +- coherence/named_map_client.go | 2 +- coherence/publisher/doc.go | 10 + coherence/publisher/publisher.go | 119 + coherence/publisher/publisher_test.go | 37 + coherence/session.go | 11 + coherence/subscriber/doc.go | 10 + coherence/subscriber/subscriber.go | 67 + coherence/topic/doc.go | 10 + coherence/topic/topic.go | 25 + coherence/topics.go | 894 +++++ coherence/v1client.go | 50 +- coherence/v1queues.go | 22 +- coherence/v1requests.go | 75 + go.sum | 2 - .../src/main/resources/test-cache-config.xml | 20 + proto/topics/topic_service_messages_v1.pb.go | 3130 +++++++++++++++++ proto/v1/proxy_service_messages_v1.pb.go | 224 +- scripts/run-compat-ce.sh | 3 + test/e2e/topics/doc.go | 10 + test/e2e/topics/suite_test.go | 17 + test/e2e/topics/topics_test.go | 197 ++ 24 files changed, 5015 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/build-topics.yaml create mode 100644 coherence/publisher/doc.go create mode 100644 coherence/publisher/publisher.go create mode 100644 coherence/publisher/publisher_test.go create mode 100644 coherence/subscriber/doc.go create mode 100644 coherence/subscriber/subscriber.go create mode 100644 coherence/topic/doc.go create mode 100644 coherence/topic/topic.go create mode 100644 coherence/topics.go create mode 100644 proto/topics/topic_service_messages_v1.pb.go create mode 100644 test/e2e/topics/doc.go create mode 100644 test/e2e/topics/suite_test.go create mode 100644 test/e2e/topics/topics_test.go diff --git a/.github/workflows/build-topics.yaml b/.github/workflows/build-topics.yaml new file mode 100644 index 00000000..4fc0afa2 --- /dev/null +++ b/.github/workflows/build-topics.yaml @@ -0,0 +1,83 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build Topics +# --------------------------------------------------------------------------- +name: CI Topics + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.03.2-SNAPSHOT + go-version: + - 1.23.x + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: E2E Topics Tests + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 INCLUDE_LONG_RUNNING=true PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/Makefile b/Makefile index d80d1c4b..258859d7 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,9 @@ CURRDIR := $(shell pwd) USER_ID := $(shell echo "`id -u`:`id -g`") override BUILD_BIN := $(CURRDIR)/bin -override PROTO_DIR := $(CURRDIR)/etc/proto +override PROTO_DIR := $(CURRDIR)/etc/proto override PROTOV1_DIR := $(CURRDIR)/etc/proto-v1 +override PROTOTOPICS_DIR := $(CURRDIR)/etc/topics # ---------------------------------------------------------------------------------------------------------------------- # Set the location of various build tools @@ -240,23 +241,30 @@ ifeq ($(SKIP_PROTO_GENERATION),true) @echo "Skipping proto generation..." else mkdir -p $(PROTOV1_DIR) || true - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/proxy_service_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/proxy_service_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/common_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/common_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/cache_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/cache_service_messages_v1.proto - curl $(CURL_AUTH) -o $(PROTOV1_DIR)/queue_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/25.03.1/prj/coherence-grpc/src/main/proto/queue_service_messages_v1.proto + mkdir -p $(PROTOTOPICS_DIR) || true + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/proxy_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/proxy_service_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/proxy_service_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/common_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/common_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/cache_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/cache_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOV1_DIR)/queue_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/queue_service_messages_v1.proto + curl $(CURL_AUTH) -o $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto https://raw.githubusercontent.com/oracle/coherence/refs/heads/main/prj/coherence-grpc/src/main/proto/topic_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/proxy_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/proxy_service_v1.proto echo "" >> $(PROTOV1_DIR)/common_messages_v1.proto echo "" >> $(PROTOV1_DIR)/cache_service_messages_v1.proto echo "" >> $(PROTOV1_DIR)/queue_service_messages_v1.proto + echo "" >> $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/proxy_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/proxy_service_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/common_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/cache_service_messages_v1.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1";' >> $(PROTOV1_DIR)/queue_service_messages_v1.proto - mkdir ./proto/v1 || true - $(TOOLS_BIN)/protoc --proto_path=./etc/proto-v1 --go_out=./proto/v1 --go_opt=paths=source_relative --go-grpc_out=./proto/v1 --go-grpc_opt=paths=source_relative etc/proto-v1/proxy_service_messages_v1.proto etc/proto-v1/proxy_service_v1.proto etc/proto-v1/common_messages_v1.proto etc/proto-v1/cache_service_messages_v1.proto etc/proto-v1/queue_service_messages_v1.proto + echo 'option go_package = "github.com/oracle/coherence-go-client/proto/v1/topics";' >> $(PROTOTOPICS_DIR)/topic_service_messages_v1.proto + mkdir ./proto/v1 ./proto/topics || true + $(TOOLS_BIN)/protoc --proto_path=./etc/proto-v1 --go_out=./proto/v1 --go_opt=paths=source_relative --go-grpc_out=./proto/v1 --go-grpc_opt=paths=source_relative etc/proto-v1/proxy_service_messages_v1.proto etc/proto-v1/proxy_service_v1.proto etc/proto-v1/common_messages_v1.proto etc/proto-v1/cache_service_messages_v1.proto etc/proto-v1/queue_service_messages_v1.proto + $(TOOLS_BIN)/protoc --proto_path=./etc/topics --proto_path=./etc/proto-v1 --go_out=./proto/topics --go_opt=paths=source_relative --go-grpc_out=./proto/topics --go-grpc_opt=paths=source_relative etc/topics/topic_service_messages_v1.proto + cat proto/topics/topic_service_messages_v1.pb.go | sed 's,^\tv1 "github.com/oracle/coherence-go-client/proto/v1",\tv1 "github.com/oracle/coherence-go-client/v2/proto/v1",' > /tmp/proto-queues + mv /tmp/proto-queues proto/topics/topic_service_messages_v1.pb.go endif # ---------------------------------------------------------------------------------------------------------------------- @@ -434,6 +442,15 @@ test-e2e-standalone-queues: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e -- $(GO_TEST_FLAGS) -v -coverprofile=$(COVERAGE_DIR)/cover-functional-queues.out -v ./e2e/queues/... -coverpkg=github.com/oracle/coherence-go-client/v2/coherence/... go tool cover -func=$(COVERAGE_DIR)/cover-functional-queues.out | grep -v '0.0%' +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the Go end to end tests for standalone Coherence with Topics +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-e2e-standalone-topics +test-e2e-standalone-topics: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e tests with Coherence queues + cd test && CGO_ENABLED=0 $(GOTESTSUM) --format testname --junitfile $(TEST_LOGS_DIR)/go-client-test-queues.xml \ + -- $(GO_TEST_FLAGS) -v -coverprofile=$(COVERAGE_DIR)/cover-functional-topics.out -v ./e2e/topics/... -coverpkg=github.com/oracle/coherence-go-client/v2/coherence/... + go tool cover -func=$(COVERAGE_DIR)/cover-functional-topics.out | grep -v '0.0%' + # ---------------------------------------------------------------------------------------------------------------------- # Executes the Go end to end tests for gRPC v1 tests # ---------------------------------------------------------------------------------------------------------------------- diff --git a/coherence/common.go b/coherence/common.go index 2919a90c..94a40f9b 100644 --- a/coherence/common.go +++ b/coherence/common.go @@ -198,10 +198,9 @@ func unregisterLifecycleListener[K comparable, V any](baseClient *baseClient[K, // executeAddIndex executes the add index operation against a baseClient. func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseClient[K, V], extractor extractors.ValueExtractor[T, E], sorted bool, comparator extractors.Comparator[E]) error { var ( - extractorSerializer = NewSerializer[any](bc.format) - binExtractor []byte - binComparator []byte - err = bc.ensureClientConnection() + binExtractor []byte + binComparator []byte + err = bc.ensureClientConnection() ) if err != nil { @@ -213,12 +212,12 @@ func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseCli defer cancel() } - binExtractor, err = extractorSerializer.Serialize(extractor) + binExtractor, err = bc.session.genericSerializer.Serialize(extractor) if err != nil { return err } - binComparator, err = extractorSerializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { return err } @@ -236,9 +235,8 @@ func executeAddIndex[K comparable, V, T, E any](ctx context.Context, bc *baseCli // executeRemoveIndex executes the remove index operation against a baseClient. func executeRemoveIndex[K comparable, V, T, E any](ctx context.Context, bc *baseClient[K, V], extractor extractors.ValueExtractor[T, E]) error { var ( - extractorSerializer = NewSerializer[any](bc.format) - binExtractor []byte - err = bc.ensureClientConnection() + binExtractor []byte + err = bc.ensureClientConnection() ) if err != nil { @@ -250,7 +248,7 @@ func executeRemoveIndex[K comparable, V, T, E any](ctx context.Context, bc *base defer cancel() } - binExtractor, err = extractorSerializer.Serialize(extractor) + binExtractor, err = bc.session.genericSerializer.Serialize(extractor) if err != nil { return err } @@ -1016,8 +1014,7 @@ func executeAggregate[K comparable, V, R any](ctx context.Context, bc *baseClien defer cancel() } - aggregatorSerializer := NewSerializer[any](bc.format) - binAggregator, err = aggregatorSerializer.Serialize(aggr) + binAggregator, err = bc.session.genericSerializer.Serialize(aggr) if err != nil { return zeroValue, err } @@ -1034,7 +1031,7 @@ func executeAggregate[K comparable, V, R any](ctx context.Context, bc *baseClien } } else if filter != nil { // filter was specified - binFilter, err = NewSerializer[any](bc.format).Serialize(filter) + binFilter, err = bc.session.genericSerializer.Serialize(filter) if err != nil { return zeroValue, err } @@ -1112,8 +1109,7 @@ func executeInvoke[K comparable, V any, R any](ctx context.Context, bc *baseClie return zeroValue, err } - procSerializer := NewSerializer[any](bc.format) - binProcessor, err = procSerializer.Serialize(proc) + binProcessor, err = bc.session.genericSerializer.Serialize(proc) if err != nil { return zeroValue, err } @@ -1174,14 +1170,13 @@ func executeInvokeAllFilterOrKeys[K comparable, V any, R any](ctx context.Contex newCtx, cancel := bc.session.ensureContext(ctx) - procSerializer := NewSerializer[any](bc.format) - if binProcessor, err = procSerializer.Serialize(proc); err != nil { + if binProcessor, err = bc.session.genericSerializer.Serialize(proc); err != nil { ch <- &StreamedEntry[K, R]{Err: err} return ch } if fltr != nil { - if binFilter, err = NewSerializer[any](bc.format).Serialize(fltr); err != nil { + if binFilter, err = bc.session.genericSerializer.Serialize(fltr); err != nil { ch <- &StreamedEntry[K, R]{Err: err} return ch } @@ -1326,7 +1321,7 @@ func executeKeySetFilter[K comparable, V any](ctx context.Context, bc *baseClien if fltr == nil { fltr = filters.Always() } - binFilter, err = NewSerializer[any](bc.format).Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedKey[K]{Err: err} return ch @@ -1906,7 +1901,6 @@ func executeEntrySetFilter[K comparable, V any, E any](ctx context.Context, bc * binFilter = make([]byte, 0) binComparator = make([]byte, 0) ch = make(chan *StreamedEntry[K, V]) - serializer = NewSerializer[any](bc.format) ) if err != nil { @@ -1919,14 +1913,14 @@ func executeEntrySetFilter[K comparable, V any, E any](ctx context.Context, bc * if fltr == nil { fltr = filters.Always() } - binFilter, err = serializer.Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedEntry[K, V]{Err: err} return ch } if comparator != nil { - binComparator, err = serializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { ch <- &StreamedEntry[K, V]{Err: err} return ch @@ -2008,7 +2002,6 @@ func executeValues[K comparable, V any, E any](ctx context.Context, bc *baseClie binFilter = make([]byte, 0) binComparator = make([]byte, 0) ch = make(chan *StreamedValue[V]) - serializer = NewSerializer[any](bc.format) ) if err != nil { @@ -2021,14 +2014,14 @@ func executeValues[K comparable, V any, E any](ctx context.Context, bc *baseClie if fltr == nil { fltr = filters.Always() } - binFilter, err = serializer.Serialize(fltr) + binFilter, err = bc.session.genericSerializer.Serialize(fltr) if err != nil { ch <- &StreamedValue[V]{Err: err} return ch } if comparator != nil { - binComparator, err = serializer.Serialize(comparator) + binComparator, err = bc.session.genericSerializer.Serialize(comparator) if err != nil { ch <- &StreamedValue[V]{Err: err} return ch diff --git a/coherence/named_map_client.go b/coherence/named_map_client.go index 53286bab..802e9274 100644 --- a/coherence/named_map_client.go +++ b/coherence/named_map_client.go @@ -35,7 +35,7 @@ func (nm *NamedMapClient[K, V]) getBaseClient() *baseClient[K, V] { //nolint // Invoke the specified processor against the entry mapped to the specified key. // Processors are invoked atomically against a specific entry as the process may mutate the entry. // The type parameter is R = type of the result of the invocation. -// +// NamedMapClient // The example below shows how to run an entry processor to increment the age of person identified by the key 1. // // namedMap, err := coherence.GetNamedMap[int, Person](session, "people") diff --git a/coherence/publisher/doc.go b/coherence/publisher/doc.go new file mode 100644 index 00000000..ad61ca6d --- /dev/null +++ b/coherence/publisher/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +/* +Package publisher provides various publisher functions and types. +*/ +package publisher diff --git a/coherence/publisher/publisher.go b/coherence/publisher/publisher.go new file mode 100644 index 00000000..4d99733a --- /dev/null +++ b/coherence/publisher/publisher.go @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package publisher + +import ( + "fmt" + "math" + "math/rand/v2" + "sync/atomic" +) + +var ( + _ OrderingOption = &OrderByDefault{} + _ OrderingOption = &OrderByRoundRobin{} +) + +// PublishStatus provides the result of a publish operation. +type PublishStatus struct { + PublishedChannel int32 + RemainingCapacity int32 +} + +// Options provides options for creating a publisher. +type Options struct { + ChannelCount int32 + Ordering OrderingOption +} + +func (o Options) String() string { + return fmt.Sprintf("options{ChannelCount:%d, ordering=%v}", o.ChannelCount, o.Ordering) +} + +// EnsurePublisherResult contains the result of an ensure publisher request. +type EnsurePublisherResult struct { + ProxyID int32 + PublisherID int64 + ChannelCount int32 +} + +func (o Options) GetChannelCount() int32 { + return o.ChannelCount +} + +func (o Options) GetOrdering() OrderingOption { + return o.Ordering +} + +// WithChannelCount returns a function to set the channels for a [Publisher]. +func WithChannelCount(channelCount int32) func(options *Options) { + return func(t *Options) { + t.ChannelCount = channelCount + } +} + +// WithDefaultOrdering returns a function to set the ordering for a [Publisher]. +func WithDefaultOrdering() func(options *Options) { + return func(t *Options) { + t.Ordering = &OrderByDefault{} + } +} + +// WithRoundRobinOrdering returns a function to set the ordering to round robin for a [Publisher]. +func WithRoundRobinOrdering() func(options *Options) { + return func(t *Options) { + t.Ordering = &OrderByRoundRobin{} + } +} + +// OrderingOption defines the type of publisher ordering. +type OrderingOption interface { + GetPublishHash() int32 +} + +// OrderByDefault defines default ordering. +type OrderByDefault struct { +} + +func (o *OrderByDefault) GetPublishHash() int32 { + // #nosec G404 -- math/rand is fine here for non-security use + return rand.Int32() +} + +func (o *OrderByDefault) String() string { + return "default" +} + +// OrderByRoundRobin defines default ordering. +type OrderByRoundRobin struct { + counter int64 +} + +func (o *OrderByRoundRobin) String() string { + return "roundRobin" +} + +func (o *OrderByRoundRobin) GetPublishHash() int32 { + newVal := atomic.AddInt64(&o.counter, 1) + + if newVal >= int64(math.MaxInt32) { + atomic.CompareAndSwapInt64(&o.counter, newVal, 0) + return 0 + } + + // #nosec G115 -- val is guaranteed to be in int32 range + return int32(newVal) +} + +//// OrderByValue defines default ordering. +//type OrderByValue[V any] struct { +// value V +//} +// +//func (r *OrderByValue[V]) GetPublishHash() int32 { +// +//} diff --git a/coherence/publisher/publisher_test.go b/coherence/publisher/publisher_test.go new file mode 100644 index 00000000..7cbc0bd1 --- /dev/null +++ b/coherence/publisher/publisher_test.go @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package publisher + +import "testing" + +func TestRoundRobinOrdering(t *testing.T) { + roundRobin := &OrderByRoundRobin{} + var ( + i int32 + ) + + for i = 1; i <= 10; i++ { + next := roundRobin.GetPublishHash() + if next != i { + t.Fatalf("expected next=%d, got %d", i, next) + } + } +} + +func TestDefaultOrdering(t *testing.T) { + defaultOrdering := &OrderByDefault{} + var ( + i int32 + ) + + for i = 1; i <= 10; i++ { + next := defaultOrdering.GetPublishHash() + if next < 0 { + t.Fatalf("expected next=%d, got %d", i, next) + } + } +} diff --git a/coherence/session.go b/coherence/session.go index 3378d1ca..c93ffa4d 100644 --- a/coherence/session.go +++ b/coherence/session.go @@ -59,12 +59,15 @@ type Session struct { conn *grpc.ClientConn dialOptions []grpc.DialOption closed bool + genericSerializer Serializer[any] mapMutex sync.RWMutex caches map[string]interface{} maps map[string]interface{} queues map[string]interface{} + topics map[string]interface{} cacheIDMap safeMap[string, int32] queueIDMap safeMap[string, int32] + topicIDMap safeMap[string, int32] lifecycleMutex sync.RWMutex lifecycleListeners []*SessionLifecycleListener sessionConnectCtx context.Context @@ -77,6 +80,7 @@ type Session struct { filterID int64 // filter id for gRPC v1 v1StreamManagerCache *streamManagerV1 v1StreamManagerQueue *streamManagerV1 + v1StreamManagerTopics *streamManagerV1 } // SessionOptions holds the session attributes like host, port, tls attributes etc. @@ -171,8 +175,10 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( maps: make(map[string]interface{}, 0), caches: make(map[string]interface{}, 0), queues: make(map[string]interface{}, 0), + topics: make(map[string]interface{}, 0), cacheIDMap: newSafeIDMap(), queueIDMap: newSafeIDMap(), + topicIDMap: newSafeIDMap(), lifecycleListeners: []*SessionLifecycleListener{}, sessOpts: &SessionOptions{ PlainText: false, @@ -221,6 +227,8 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( return nil, ErrInvalidFormat } + session.genericSerializer = NewSerializer[any](session.sessOpts.Format) + // if no address option sent in then use the env or defaults if session.sessOpts.Address == "" { session.sessOpts.Address = getStringValueFromEnvVarOrDefault(envHostName, "localhost:1408") @@ -401,6 +409,9 @@ func (s *Session) getCacheID(cache string) *int32 { func (s *Session) getQueueID(queue string) *int32 { return s.queueIDMap.Get(queue) } +func (s *Session) getTopicID(topic string) *int32 { + return s.topicIDMap.Get(topic) +} func (s *Session) getCacheNameFromCacheID(cacheID int32) *string { return s.cacheIDMap.KeyFromValue(cacheID) diff --git a/coherence/subscriber/doc.go b/coherence/subscriber/doc.go new file mode 100644 index 00000000..ff6ccc48 --- /dev/null +++ b/coherence/subscriber/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +/* +Package subscriber provides various subscriber functions and types. +*/ +package subscriber diff --git a/coherence/subscriber/subscriber.go b/coherence/subscriber/subscriber.go new file mode 100644 index 00000000..0d388b87 --- /dev/null +++ b/coherence/subscriber/subscriber.go @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package subscriber + +import ( + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/filters" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" +) + +type ReceiveStatus int32 + +const ( + ReceiveSuccess = pb1topics.ReceiveStatus_ReceiveSuccess + ChannelExhausted = pb1topics.ReceiveStatus_ChannelExhausted + ChannelNotAllocatedChannel = pb1topics.ReceiveStatus_ChannelNotAllocatedChannel + UnknownSubscriber = pb1topics.ReceiveStatus_UnknownSubscriber +) + +// EnsureSubscriberResult contains the result of an ensure subscriber request. +type EnsureSubscriberResult struct { + ProxyID int32 + SubscriberID int64 + SubscriberGroupID int64 + UUID string +} + +type ReceiveResult[V any] struct { + Status ReceiveStatus +} + +// Options provides options for creating a subscriber. +type Options struct { + SubscriberGroup *string + Filter filters.Filter +} + +// TODO: Additional options +//// the optional name of the subscriber group +//SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` +//// an optional Filter to filter received messages +//Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` +//// an optional ValueExtractor to convert received messages +//Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` +//// True to return an empty value if the topic is empty + +// InSubscriberGroup returns a function to set the subscriber group for a [Subscriber]. +func InSubscriberGroup(group string) func(options *Options) { + return func(s *Options) { + s.SubscriberGroup = &group + } +} + +// WithFilter returns a function to set the [filters.Filter] for a [Subscriber]. +func WithFilter(fltr filters.Filter) func(options *Options) { + return func(s *Options) { + s.Filter = fltr + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{SubscriberGroup=%v, filter=%v}", o.SubscriberGroup, o.Filter) +} diff --git a/coherence/topic/doc.go b/coherence/topic/doc.go new file mode 100644 index 00000000..e51b646f --- /dev/null +++ b/coherence/topic/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +/* +Package topic provides various topic and functions and types. +*/ +package topic diff --git a/coherence/topic/topic.go b/coherence/topic/topic.go new file mode 100644 index 00000000..c5cc5162 --- /dev/null +++ b/coherence/topic/topic.go @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topic + +import "fmt" + +// Options provides options for creating topic. +type Options struct { + ChannelCount int32 +} + +// WithChannelCount returns a function to set the default channel count on a [NamedTopic]. +func WithChannelCount(channelCount int32) func(cacheOptions *Options) { + return func(t *Options) { + t.ChannelCount = channelCount + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{ChannelCount=%d}", o.ChannelCount) +} diff --git a/coherence/topics.go b/coherence/topics.go new file mode 100644 index 00000000..8d0d80f3 --- /dev/null +++ b/coherence/topics.go @@ -0,0 +1,894 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package coherence + +import ( + "context" + "errors" + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/publisher" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/topic" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" + pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/wrapperspb" + "strings" + "sync" +) + +const ( + // TopicDestroyed raised when a queue is destroyed usually as a result of a call to NamedTopic.Destroy(). + TopicDestroyed TopicLifecycleEventType = "topic_destroyed" + // + //// QueueTruncated raised when a queue is truncated. + //QueueTruncated TopicLifecycleEventType = "queue_truncated" + + // TopicReleased raised when a topic is released but the session. + TopicReleased TopicLifecycleEventType = "topic_released" + + defaultChannelCount = 17 +) + +var ( + _ Publisher[string] = &topicPublisher[string]{} + _ NamedTopic[string] = &baseTopicsClient[string]{} + + ErrTopicDestroyedOrReleased = errors.New("this topic has been destroyed or released") + ErrTopicsNoSupported = errors.New("the coherence server version must support protocol version 1 or above to use topic") + ErrTopicFull = errors.New("unable to publish, this topic is full") + ErrPublisherClosed = errors.New("publisher has been closed and is no longer usable") + ErrSubscriberClosed = errors.New("subscriber has been closed and is no longer usable") +) + +type TopicLifecycleEventType string + +// NamedTopic defines the APIs to interact with Coherence topic allowing to publish and +// subscribe from Go client. +// +// The type parameter is V = type of the value. +type NamedTopic[V any] interface { + // Destroy destroys this topic on the server and releases all resources. After this operation it is no longer usable on the client or server.. + Destroy(ctx context.Context) error + + CreatePublisher(ctx context.Context, options ...func(o *publisher.Options)) (Publisher[V], error) + CreateSubscriber(ctx context.Context, options ...func(o *subscriber.Options)) (Subscriber[V], error) +} + +type Publisher[V any] interface { + GetProxyID() int32 + GetPublisherID() int64 + GetChannelCount() int32 + Publish(ctx context.Context, value V) (*publisher.PublishStatus, error) + Close(ctx context.Context) error +} + +type Subscriber[V any] interface { + Close(ctx context.Context) error +} + +// GetNamedTopic gets a [NamedTopic] of the generic type specified or if a cache already exists with the +// same type parameters, it will return it otherwise it will create a new one. +func GetNamedTopic[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *topic.Options)) (NamedTopic[V], error) { + var ( + existingTopic interface{} + ok bool + err error + ) + + topicOptions := &topic.Options{ + ChannelCount: defaultChannelCount, + } + + // apply any cache options + for _, f := range options { + f(topicOptions) + } + + // protect updates to maps + session.mapMutex.Lock() + + // check to see if we already have an entry for the queue + if existingTopic, ok = session.topics[topicName]; ok { + defer session.mapMutex.Unlock() + + existing, ok2 := existingTopic.(NamedTopic[V]) + if !ok2 { + // the casting failed so return an error indicating the queue exists with different type mappings + return nil, getExistingError("NamedTopic", topicName) + } + + // check any topic options + + session.debug("using existing NamedTopic: %v", existing) + return existing, nil + } + + session.topics[topicName] = nil + session.mapMutex.Unlock() + + bt, err := ensureTopicInternal[V](ctx, session, topicName, topicOptions) + if err != nil { + return nil, err + } + + newTopic := &namedTopic[V]{baseTopicsClient: bt} + + session.topics[topicName] = newTopic + + _, err = session.v1StreamManagerTopics.ensureChannelCount(ctx, topicName, topicOptions) + if err != nil { + return nil, err + } + + return newTopic, nil +} + +type topicPublisher[V any] struct { + session *Session + isClosed bool + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + publisherID int64 + channelCount int32 + options *publisher.Options + valueSerializer Serializer[V] +} + +func (tp *topicPublisher[V]) GetProxyID() int32 { + return tp.proxyID +} + +func (tp *topicPublisher[V]) GetPublisherID() int64 { + return tp.publisherID +} + +func (tp *topicPublisher[V]) GetChannelCount() int32 { + return tp.channelCount +} + +func (tp *topicPublisher[V]) Publish(ctx context.Context, value V) (*publisher.PublishStatus, error) { + if tp.isClosed { + return nil, ErrPublisherClosed + } + + publishChannel := tp.ensureTopicChannel() + + binValue, err := tp.valueSerializer.Serialize(value) + if err != nil { + return nil, err + } + + return tp.session.v1StreamManagerTopics.publishToTopic(ctx, tp.proxyID, publishChannel, binValue) +} + +func (tp *topicPublisher[V]) Close(ctx context.Context) error { + if tp.isClosed { + return ErrPublisherClosed + } + tp.isClosed = true + return tp.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, tp.proxyID, pb1topics.TopicServiceRequestType_DestroyPublisher) +} + +func (tp *topicPublisher[V]) String() string { + return fmt.Sprintf("publisher{publisherID=%d, topicName=%s, proxyID=%d, channelCount=%d, %v, destroyed=%v}", + tp.publisherID, tp.topicName, tp.proxyID, tp.channelCount, tp.options, tp.isClosed) +} + +// ensureTopicChannel returns the channel to publish to based upon the [publisher.Options]. +func (tp *topicPublisher[V]) ensureTopicChannel() int32 { + return tp.options.GetOrdering().GetPublishHash() % tp.channelCount +} + +type baseTopicsClient[V any] struct { + session *Session + valueSerializer Serializer[V] + name string + ctx context.Context + topicID int32 + isDestroyed bool + isReleased bool + mutex *sync.RWMutex + topicOpts *topic.Options +} + +type namedTopic[V any] struct { + *baseTopicsClient[V] +} + +func (bt *baseTopicsClient[V]) Destroy(ctx context.Context) error { + return releaseTopicInternal[V](ctx, bt, true) +} + +func (bt *baseTopicsClient[V]) String() string { + return fmt.Sprintf("topic{name=%s, topicID=%d, isReleased=%v, %v}", bt.name, bt.topicID, bt.isReleased, bt.topicOpts) +} + +func (bt *baseTopicsClient[V]) CreatePublisher(ctx context.Context, options ...func(cache *publisher.Options)) (Publisher[V], error) { + return CreatePublisher[V](ctx, bt.session, bt.name, options...) +} + +func (bt *baseTopicsClient[V]) CreateSubscriber(ctx context.Context, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { + return CreateSubscriber[V](ctx, bt.session, bt.name, options...) +} + +func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publisher.EnsurePublisherResult, topicName string, options *publisher.Options) (Publisher[V], error) { + tp := &topicPublisher[V]{ + namedTopic: bt, + publisherID: result.PublisherID, + session: session, + options: options, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + topicName: topicName, + proxyID: result.ProxyID, + channelCount: result.ChannelCount, + isClosed: false, + } + return tp, nil +} + +func newSubscriber[V any](session *Session, bt *baseTopicsClient[V], result *subscriber.EnsureSubscriberResult, topicName string, options *subscriber.Options) (Subscriber[V], error) { + ts := &topicSubscriber[V]{ + namedTopic: bt, + SubscriberID: result.SubscriberID, + UUID: result.UUID, + session: session, + options: options, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + topicName: topicName, + proxyID: result.ProxyID, + disconnected: true, + isClosed: false, + } + return ts, nil +} + +// generateQueueLifecycleEvent emits the queue lifecycle events. +func (bt *baseTopicsClient[V]) generateTopicLifecycleEvent(_ interface{}, _ TopicLifecycleEventType) { + //listeners := bq.lifecycleListenersV1 + // + //if namedQ, ok := client.(NamedQueue[V]); ok || client == nil { + // event := newQueueLifecycleEvent(namedQ, eventType) + // for _, l := range listeners { + // e := *l + // e.getEmitter().emit(eventType, event) + // } + // + // if eventType == QueueDestroyed { + // bq.session.debugConnection("received destroy for queue: %s", bq.name) + // _ = releaseInternal[V](context.Background(), bq, true) + // } + //} +} + +type topicSubscriber[V any] struct { + session *Session + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + SubscriberID int64 + SubscriberGroupID int64 + channelCount int32 + options *subscriber.Options + valueSerializer Serializer[V] + UUID string + mutex sync.RWMutex + disconnected bool + isClosed bool +} + +func (ts *topicSubscriber[V]) String() string { + return fmt.Sprintf("subscriber{subscriberID=%d, subscriberGroup=%d, topicName=%s, channelCount=%d, options=%v}", ts.SubscriberID, ts.SubscriberGroupID, + ts.topicName, ts.channelCount, ts.options) +} + +func (ts *topicSubscriber[V]) Close(ctx context.Context) error { + if ts.isClosed { + return ErrSubscriberClosed + } + err := ts.ensureConnected() + if err != nil { + return err + } + + ts.isClosed = true + return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) +} + +func (ts *topicSubscriber[V]) isDisconnected() bool { + ts.mutex.Lock() + defer ts.mutex.Unlock() + return ts.disconnected +} + +// ensureConnected ensures the topic is connected. +func (ts *topicSubscriber[V]) ensureConnected() error { + if !ts.isDisconnected() { + return nil + } + + return nil +} + +func newBaseTopicsClient[V any](ctx context.Context, session *Session, queueName string, topicID int32, topicOpts *topic.Options) (*baseTopicsClient[V], error) { + if session.closed { + return nil, ErrClosed + } + + bq := baseTopicsClient[V]{ + ctx: ctx, + name: queueName, + topicID: topicID, + session: session, + valueSerializer: NewSerializer[V](session.sessOpts.Format), + mutex: &sync.RWMutex{}, + topicOpts: topicOpts, + } + + return &bq, nil +} + +func ensurePublisherOptions(options ...func(cache *publisher.Options)) *publisher.Options { + publisherOptions := &publisher.Options{ + ChannelCount: defaultChannelCount, + Ordering: &publisher.OrderByDefault{}, + } + + // apply any cache options + for _, f := range options { + f(publisherOptions) + } + + return publisherOptions +} + +// CreatePublisher creates a topic publisher. +func CreatePublisher[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *publisher.Options)) (Publisher[V], error) { + publisherOptions := ensurePublisherOptions(options...) + + if session.v1StreamManagerTopics == nil { + if err := ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + } + + result, err := session.v1StreamManagerTopics.ensurePublisher(ctx, topicName, publisherOptions.ChannelCount) + if err != nil { + return nil, err + } + + return newPublisher[V](session, nil, result, topicName, publisherOptions) +} + +// CreateSubscriber creates a topic subscriber. +func CreateSubscriber[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { + var ( + subscriberOptions = &subscriber.Options{} + binFilter []byte + err error + ) + + // apply any subscriber options + for _, f := range options { + f(subscriberOptions) + } + + if session.v1StreamManagerTopics == nil { + if err = ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + } + + if subscriberOptions.Filter != nil { + binFilter, err = session.genericSerializer.Serialize(subscriberOptions.Filter) + if err != nil { + return nil, err + } + } + + result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, binFilter) + if err != nil { + return nil, err + } + + return newSubscriber[V](session, nil, result, topicName, subscriberOptions) +} + +// ensureChannelCount issues the ensure topic channels +func (m *streamManagerV1) ensureChannelCount(ctx context.Context, topicName string, opts *topic.Options) (*int32, error) { + req, err := m.newEnsureChannelRequest(topicName, opts.ChannelCount, opts.ChannelCount) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureChannelCount) + if err != nil { + return nil, err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, true) + if err1 != nil { + return nil, err1 + } + + var message = &wrapperspb.Int32Value{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + actualChannelCount := &message.Value + + return actualChannelCount, nil +} + +// ensurePublisher ensures a publisher. +func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, channelCount int32) (*publisher.EnsurePublisherResult, error) { + req, err := m.newEnsurePublisherRequest(topicName, channelCount) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsurePublisher) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + var message = &pb1topics.EnsurePublisherResponse{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return &publisher.EnsurePublisherResult{ + PublisherID: message.PublisherId, + ProxyID: message.ProxyId, + ChannelCount: message.ChannelCount, + }, nil +} + +// ensureSubscriber ensures a subscriber. +func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, binFilter []byte) (*subscriber.EnsureSubscriberResult, error) { + req, err := m.newEnsureSubscriberRequest(topicName, binFilter) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscriber) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + var message = &pb1topics.EnsureSubscriberResponse{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure subscriber response", err) + return nil, err + } + + s := subscriber.EnsureSubscriberResult{ProxyID: message.ProxyId} + + if message.SubscriberId != nil { + s.SubscriberID = message.SubscriberId.Id + s.UUID = string(message.SubscriberId.Uuid) + } + + return &s, nil +} + +// destroyPublisher destroyed a publisher. +func (m *streamManagerV1) destroyPublisherOrSubscriber(ctx context.Context, proxyID int32, reqType pb1topics.TopicServiceRequestType) error { + req, err := m.newDestroyPublisherOrSubscriberRequest(proxyID, reqType) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, reqType) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + _, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return err + } + + return nil +} + +// +//// ensureSubscriber ensures a subscriber. +//func (m *streamManagerV1) ensureSubscriptionRequest(ctx context.Context, subscriptionID int64, force bool) (*subscriber.EnsureSubscriberResult, error) { +// req, err := m.newEnsureSubscriptionRequest(subscriptionID, force) +// if err != nil { +// return nil, err +// } +// +// requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscription) +// +// newCtx, cancel := m.session.ensureContext(ctx) +// if cancel != nil { +// defer cancel() +// } +// +// defer m.cleanupRequest(req.Id) +// +// result, err1 := waitForResponse(newCtx, requestType.ch, false) +// if err1 != nil { +// return nil, err +// } +// +// var message = &pb1topics.EnsureSubscriptionRequest{} +// if err = result.UnmarshalTo(message); err != nil { +// err = getUnmarshallError("ensure subscriber response", err) +// return nil, err +// } +// +// s := subscriber.EnsureSubscriberResult{ProxyID: message.ProxyId} +// +// if message.SubscriberId != nil { +// s.SubscriberID = message.SubscriberId.Id +// s.UUID = string(message.SubscriberId.Uuid) +// } +// +// return &s, nil +//} + +// ensureTopic issues the ensure queue request. This must be done before any requests to access queues can be issued. +func (m *streamManagerV1) ensureTopic(ctx context.Context, topicName string) (*int32, error) { + return m.ensure(ctx, topicName, m.session.topicIDMap, -1, true) +} + +func ensureTopicInternal[V any](ctx context.Context, session *Session, topicName string, topicsOpts *topic.Options) (*baseTopicsClient[V], error) { + if err := ensureV1StreamManagerTopics(session); err != nil { + return nil, err + } + + topicID, err := session.v1StreamManagerTopics.ensureTopic(ctx, topicName) + if err != nil { + return nil, err + } + + // ensure channels + _, err = session.v1StreamManagerTopics.ensureTopicChannels(ctx, topicName, topicsOpts.ChannelCount) + if err != nil { + return nil, err + } + + return newBaseTopicsClient[V](ctx, session, topicName, *topicID, topicsOpts) +} + +// ensure ensures a queue or cache. +func (m *streamManagerV1) ensureTopicChannels(ctx context.Context, topicName string, channelCount int32) (*int32, error) { + var ID *int32 + + req, err := m.newEnsureTopicChannelRequest(topicName, channelCount) + + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureChannelCount) + + if err != nil { + return nil, err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, true) + if err1 != nil { + return nil, err1 + } + + var message = &wrapperspb.Int32Value{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return ID, nil +} + +// ensure ensures a queue or cache. +func (m *streamManagerV1) publishToTopic(ctx context.Context, proxyID int32, publishChannel int32, value []byte) (*publisher.PublishStatus, error) { + req, err := m.newPublishRequest(proxyID, publishChannel, value) + + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_Publish) + + if err != nil { + return nil, err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return nil, err1 + } + + var message = &pb1topics.PublishResult{} + if err = result.UnmarshalTo(message); err != nil { + err = getUnmarshallError("ensure response", err) + return nil, err + } + + return &publisher.PublishStatus{PublishedChannel: publishChannel, RemainingCapacity: message.RemainingCapacity}, nil +} + +func ensureV1StreamManagerTopics(session *Session) error { + session.connectMutex.Lock() + defer session.connectMutex.Unlock() + if session.v1StreamManagerTopics == nil { + topicsManger, err := newStreamManagerV1(session, topicServiceProtocol) + if err != nil { + if strings.Contains(err.Error(), "Method not found") { + return ErrTopicsNoSupported + } + return err + } + session.v1StreamManagerTopics = topicsManger + } + + return nil +} + +func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], destroy bool) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.session.mapMutex.Lock() + defer bt.session.mapMutex.Unlock() + + if destroy { + newCtx, cancel := bt.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + err := bt.session.v1StreamManagerTopics.genericTopicRequest(newCtx, pb1topics.TopicServiceRequestType_DestroyTopic, bt.name) + if err != nil { + return err + } + bt.isDestroyed = true + } else { + if existingQueue, ok := bt.session.topics[bt.name]; ok { + bt.generateTopicLifecycleEvent(existingQueue, TopicReleased) + bt.isReleased = true + } + } + + delete(bt.session.topics, bt.name) + bt.session.topicIDMap.Remove(bt.name) + + return nil +} + +func (m *streamManagerV1) newEnsureChannelRequest(topicName string, requiredCount, channelCount int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureChannelCountRequest{ + Topic: &topicName, + RequiredCount: requiredCount, + ChannelCount: &channelCount, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureChannelCount, anyReq) +} + +func (m *streamManagerV1) newEnsurePublisherRequest(topicName string, channelCount int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsurePublisherRequest{ + Topic: topicName, + ChannelCount: channelCount, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsurePublisher, anyReq) +} + +func (m *streamManagerV1) newDestroyPublisherOrSubscriberRequest(proxyID int32, requestType pb1topics.TopicServiceRequestType) (*pb1.ProxyRequest, error) { + req := &wrapperspb.Int32Value{ + Value: proxyID, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest("", requestType, anyReq) +} + +// TODO: Add more options +func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, binFilter []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureSubscriberRequest{ + Topic: topicName, + Filter: binFilter, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriber, anyReq) +} + +//func (m *streamManagerV1) newInitializeSubscriptionRequest(subscriptionID int64, force bool) (*pb1.ProxyRequest, error) { +// req := &pb1topics.InitializeSubscriptionRequest{ +// SubscriptionId: subscriptionID, +// ForceReconnect: force, +// } +// +// anyReq, err := anypb.New(req) +// if err != nil { +// return nil, err +// } +// return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureSubscription, anyReq) +//} + +// genericTopicRequest issues a generic topic request that is further defined by the reqType. +func (m *streamManagerV1) genericTopicRequest(ctx context.Context, reqType pb1topics.TopicServiceRequestType, topic string) error { + var ( + err error + req *pb1.ProxyRequest + isDestroy = false + value *anypb.Any + ) + + if reqType == pb1topics.TopicServiceRequestType_DestroyTopic { + isDestroy = true + } + + if isDestroy { + // destroy requires sending of the topic name in the message + value, err = stringToAny(topic) + if err != nil { + return err + } + req, err = m.newWrapperProxyTopicsRequest(topic, reqType, value) + } else { + req, err = m.newGenericNamedTopicRequest(topic, reqType) + } + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, reqType) + if err != nil { + return err + } + + // we must now wait for a response + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + // remove the entry from the channel + defer m.cleanupRequest(req.Id) + + if isDestroy { + // special case for destroy + return nil + } + + _, err1 := waitForResponse(newCtx, requestType.ch) + if err1 != nil { + return err1 + } + + return nil +} + +func (m *streamManagerV1) newWrapperProxyTopicsRequest(topicName string, requestType pb1topics.TopicServiceRequestType, message *anypb.Any) (*pb1.ProxyRequest, error) { + var ( + topicID *int32 + zeroTopic int32 + noTopicRequired = requestType == pb1topics.TopicServiceRequestType_DestroyTopic || + requestType == pb1topics.TopicServiceRequestType_EnsurePublisher || + requestType == pb1topics.TopicServiceRequestType_EnsureSubscription || + requestType == pb1topics.TopicServiceRequestType_EnsureSubscriber + ) + + // validate the topic ID if it is not an ensure queue request + if topicName != "" && !noTopicRequired { + topicID = m.session.getTopicID(topicName) + if topicID == nil { + return nil, getTopicIDMessage(topicName) + } + } + + // special cases for topic requests that require no topic + if noTopicRequired { + topicID = &zeroTopic + } + + ncRequest, err := newNamedTopicRequest(topicID, requestType, message) + + if err != nil { + return nil, err + } + + return m.newProxyRequest(ncRequest), nil +} + +func (m *streamManagerV1) newWrapperProxyPublisherRequest(proxyID int32, requestType pb1topics.TopicServiceRequestType, message *anypb.Any) (*pb1.ProxyRequest, error) { + ncRequest, err := newNamedTopicRequest(&proxyID, requestType, message) + + if err != nil { + return nil, err + } + + return m.newProxyRequest(ncRequest), nil +} + +func stringToAny(topicName string) (*anypb.Any, error) { + strValue := wrapperspb.String(topicName) + return anypb.New(strValue) +} + +func newNamedTopicRequest(topicID *int32, reqType pb1topics.TopicServiceRequestType, message *anypb.Any) (*anypb.Any, error) { + req := &pb1topics.TopicServiceRequest{ + Type: reqType, + ProxyId: topicID, + Message: message, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return anyReq, nil +} diff --git a/coherence/v1client.go b/coherence/v1client.go index 69b4bee4..fdb0c3dd 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" "github.com/oracle/coherence-go-client/v2/proto/v1" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/grpc/codes" @@ -43,10 +44,12 @@ const ( protocolVersion = 1 cacheServiceProtocol V1ProxyProtocol = "CacheService" queueServiceProtocol V1ProxyProtocol = "QueueService" + topicServiceProtocol V1ProxyProtocol = "TopicService" errorFormat = "error: %v" responseDebug = "received response: %v" namedCacheResponseTypeURL = "type.googleapis.com/coherence.cache.v1.NamedCacheResponse" namedQueueResponseTypeURL = "type.googleapis.com/coherence.concurrent.queue.v1.NamedQueueResponse" + namedTopicResponseTypeURL = "type.googleapis.com/coherence.topic.v1.TopicServiceResponse" ) // responseMessages is a response received by a waiting client. @@ -54,6 +57,7 @@ type responseMessage struct { message *anypb.Any namedCacheResponse *pb1.NamedCacheResponse namedQueueResponse *pb1.NamedQueueResponse + namedTopicResponse *pb1topics.TopicServiceResponse complete bool err *string } @@ -64,6 +68,10 @@ func (rm responseMessage) String() string { if rm.namedCacheResponse != nil { sb.WriteString(fmt.Sprintf(", cacheId=%v", rm.namedCacheResponse.CacheId)) + } else if rm.namedTopicResponse != nil { + sb.WriteString(fmt.Sprintf(", topicId=%v", rm.namedTopicResponse.ProxyId)) + } else if rm.namedQueueResponse != nil { + sb.WriteString(fmt.Sprintf(", queueId=%v", rm.namedQueueResponse.QueueId)) } sb.WriteString("}") @@ -152,10 +160,8 @@ func (m *streamManagerV1) ensureStream() (*eventStreamV1, error) { // check that we received an InitResponse response := proxyResponse.GetInit() if response == nil { - if err != nil { - cancel() - return nil, errors.New("did not receive an InitResponse") - } + cancel() + return nil, errors.New("did not receive an InitResponse") } // save the server information received @@ -226,6 +232,13 @@ func (m *streamManagerV1) processResponseMessage(id int64, resp *responseMessage return } resp.namedQueueResponse = &namedQueueResponse + case namedTopicResponseTypeURL: + var namedTopicResponse pb1topics.TopicServiceResponse + if err := resp.message.UnmarshalTo(&namedTopicResponse); err != nil { + logMessage(WARNING, "%v", getUnmarshallError("namedTopicResponse", err)) + return + } + resp.namedTopicResponse = &namedTopicResponse default: logMessage(WARNING, "Unknown message type: %v", resp.message.TypeUrl) @@ -325,7 +338,6 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { // process queue event if reqID == 0 && resp.namedQueueResponse != nil { - // find the queueName from the queueID queueName := m.session.queueIDMap.KeyFromValue(resp.namedQueueResponse.QueueId) if queueName == nil { @@ -345,6 +357,8 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { return } + // process topic events here- TODO + m.session.debugConnection("id: %v Response: %v", reqID, resp) // received a named cache or queue response, so write the response to the channel for the originating request @@ -383,17 +397,24 @@ func (m *streamManagerV1) ensureCache(ctx context.Context, cache string) (*int32 } // ensure ensures a queue or cache. -func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap[string, int32], queueType NamedQueueType) (*int32, error) { +func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap[string, int32], queueType NamedQueueType, isTopicFlag ...bool) (*int32, error) { var ( ID *int32 err error req *v1.ProxyRequest isQueue = queueType >= 0 + isTopic bool requestType proxyRequestChannel ) + if len(isTopicFlag) > 0 && isTopicFlag[0] { + isTopic = true + } + if isQueue { req, err = m.newEnsureQueueRequest(name, queueType) + } else if isTopic { + req, err = m.newEnsureTopicRequest(name) } else { // cache req, err = m.newEnsureCacheRequest(name) @@ -405,6 +426,8 @@ func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap if isQueue { requestType, err = m.submitQueueRequest(req, pb1.NamedQueueRequestType_EnsureQueue) + } else if isTopic { + requestType, err = m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureTopic) } else { requestType, err = m.submitRequest(req, pb1.NamedCacheRequestType_EnsureCache) } @@ -1216,7 +1239,7 @@ func (m *streamManagerV1) cleanupRequest(reqID int64) { close(requestType.ch) } -// defaultFunction returns the default named cache message +// defaultFunction returns the default named cache, queue or topic message. var defaultFunction = func(resp responseMessage) *anypb.Any { if resp.namedCacheResponse != nil && resp.namedCacheResponse.Message != nil { return resp.namedCacheResponse.Message @@ -1224,6 +1247,9 @@ var defaultFunction = func(resp responseMessage) *anypb.Any { if resp.namedQueueResponse != nil && resp.namedQueueResponse.Message != nil { return resp.namedQueueResponse.Message } + if resp.namedTopicResponse != nil && resp.namedTopicResponse.Message != nil { + return resp.namedTopicResponse.Message + } return nil } @@ -1232,7 +1258,7 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... var ( err error result *anypb.Any - isEnsure = len(ensure) != 0 + isEnsure = len(ensure) != 0 && ensure[0] ) // wait until we get a complete request, or we time out @@ -1247,7 +1273,7 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... resp.complete = true } - if resp.namedCacheResponse != nil || resp.namedQueueResponse != nil { + if resp.namedCacheResponse != nil || resp.namedQueueResponse != nil || resp.namedTopicResponse != nil { // unpack the result message if we have valid named cache response if !isEnsure { // standard case where we want to return the named cache/queue result message @@ -1256,6 +1282,8 @@ func waitForResponse(newCtx context.Context, ch chan responseMessage, ensure ... // special case for ensure, return the cache id or queue id, and it will be handled by ensure if resp.namedCacheResponse != nil { result, err = anypb.New(wrapperspb.Int32(resp.namedCacheResponse.CacheId)) + } else if resp.namedTopicResponse != nil { + result, err = anypb.New(wrapperspb.Int32(resp.namedTopicResponse.ProxyId)) } else { result, err = anypb.New(wrapperspb.Int32(resp.namedQueueResponse.QueueId)) } @@ -1405,6 +1433,10 @@ func getQueueIDMessage(cache string) error { return fmt.Errorf("unable to find queue id for queue named [%s]", cache) } +func getTopicIDMessage(topic string) error { + return fmt.Errorf("unable to find topic id for topic named [%s]", topic) +} + func getUnmarshallError(message string, err error) error { return fmt.Errorf("unable to unpack %v message %v", message, err) } diff --git a/coherence/v1queues.go b/coherence/v1queues.go index 44816b47..5218a483 100644 --- a/coherence/v1queues.go +++ b/coherence/v1queues.go @@ -8,6 +8,7 @@ package coherence import ( "context" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -20,7 +21,7 @@ func (m *streamManagerV1) ensureQueue(ctx context.Context, queue string, queueTy return m.ensure(ctx, queue, m.session.queueIDMap, queueType) } -// submitRequest submits a request to the stream manager and returns named queue request. +// submitQueueRequest submits a request to the stream manager and returns named queue request. func (m *streamManagerV1) submitQueueRequest(req *pb1.ProxyRequest, requestType pb1.NamedQueueRequestType) (proxyRequestChannel, error) { m.mutex.Lock() defer m.mutex.Unlock() @@ -37,7 +38,24 @@ func (m *streamManagerV1) submitQueueRequest(req *pb1.ProxyRequest, requestType return r, m.eventStream.grpcStream.Send(req) } -// genericCacheRequest issues a generic request that is further defined by the reqType. +// submitTopicRequest submits a request to the stream manager and returns named topic request. +func (m *streamManagerV1) submitTopicRequest(req *pb1.ProxyRequest, requestType pb1topics.TopicServiceRequestType) (proxyRequestChannel, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + // create a channel for the response + ch := make(chan responseMessage) + + r := proxyRequestChannel{ch: ch} + + // save the request in the map keyed by request id + m.requests[req.Id] = r + m.session.debugConnection("id: %v submit queue request: %v %v", req.Id, requestType, req) + + return r, m.eventStream.grpcStream.Send(req) +} + +// genericQueueRequest issues a generic request that is further defined by the reqType. func (m *streamManagerV1) genericQueueRequest(ctx context.Context, reqType pb1.NamedQueueRequestType, queue string) error { req, err := m.newGenericNamedQueueRequest(queue, reqType) if err != nil { diff --git a/coherence/v1requests.go b/coherence/v1requests.go index b486bd36..35d4491c 100644 --- a/coherence/v1requests.go +++ b/coherence/v1requests.go @@ -7,9 +7,13 @@ package coherence import ( + "fmt" + pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" + "os" + "runtime" "time" ) @@ -25,6 +29,7 @@ func (m *streamManagerV1) newInitRequest() *pb1.ProxyRequest { ProtocolVersion: protocolVersion, SupportedProtocolVersion: protocolVersion, Protocol: string(m.proxyProtocol), + Identity: generateClientMemberIdentity(), } pr := pb1.ProxyRequest{ @@ -37,6 +42,30 @@ func (m *streamManagerV1) newInitRequest() *pb1.ProxyRequest { return &pr } +func generateClientMemberIdentity() *pb1.ClientMemberIdentity { + processID := fmt.Sprintf("PID=%d, OS=%v, Arch=%v", os.Getpid(), runtime.GOOS, runtime.GOARCH) + hostname, _ := os.Hostname() + clusterName := getEnvOrNil("COHERENCE_CLUSTER") + rackName := getEnvOrNil("COHERENCE_RACK") + siteName := getEnvOrNil("COHERENCE_SITE") + + return &pb1.ClientMemberIdentity{ + ProcessName: &processID, + MachineName: &hostname, + ClusterName: clusterName, + RackName: rackName, + SiteName: siteName, + } +} + +func getEnvOrNil(key string) *string { + value := getStringValueFromEnvVarOrDefault(key, "") + if value == "" { + return nil + } + return &value +} + func (m *streamManagerV1) newEnsureCacheRequest(cache string) (*pb1.ProxyRequest, error) { req := &pb1.EnsureCacheRequest{ Cache: cache, @@ -64,6 +93,48 @@ func (m *streamManagerV1) newEnsureQueueRequest(queue string, queueType NamedQue return m.newWrapperProxyQueueRequest("", pb1.NamedQueueRequestType_EnsureQueue, anyReq) } +func (m *streamManagerV1) newEnsureTopicRequest(topicName string) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureTopicRequest{ + Topic: topicName, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureTopic, anyReq) +} + +func (m *streamManagerV1) newEnsureTopicChannelRequest(topicName string, channelCount int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureChannelCountRequest{ + Topic: &topicName, + ChannelCount: &channelCount, + RequiredCount: channelCount, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureChannelCount, anyReq) +} + +func (m *streamManagerV1) newPublishRequest(proxyID int32, publishChannel int32, value []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.PublishRequest{ + Channel: publishChannel, + Values: [][]byte{value}, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + + return m.newWrapperProxyPublisherRequest(proxyID, pb1topics.TopicServiceRequestType_Publish, anyReq) +} + func getQueueType(queueType NamedQueueType) pb1.NamedQueueType { if queueType == Queue { return pb1.NamedQueueType_Queue @@ -82,6 +153,10 @@ func (m *streamManagerV1) newGenericNamedQueueRequest(cache string, requestType return m.newWrapperProxyQueueRequest(cache, requestType, nil) } +func (m *streamManagerV1) newGenericNamedTopicRequest(topicName string, requestType pb1topics.TopicServiceRequestType) (*pb1.ProxyRequest, error) { + return m.newWrapperProxyTopicsRequest(topicName, requestType, nil) +} + func (m *streamManagerV1) newGetRequest(cache string, key []byte) (*pb1.ProxyRequest, error) { return m.newSingleValueBasedRequest(pb1.NamedCacheRequestType_Get, cache, key) } diff --git a/go.sum b/go.sum index e22d2bc4..67b529a9 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,6 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= diff --git a/java/coherence-go-test/src/main/resources/test-cache-config.xml b/java/coherence-go-test/src/main/resources/test-cache-config.xml index d6d9b770..222767b8 100644 --- a/java/coherence-go-test/src/main/resources/test-cache-config.xml +++ b/java/coherence-go-test/src/main/resources/test-cache-config.xml @@ -27,6 +27,13 @@ + + + * + topic-scheme + + + distributed-scheme @@ -90,5 +97,18 @@ + + + topic-scheme + ${coherence.service.name Partitioned}Topic + true + ${coherence.distributed.partitioncount 257} + true + + {topic-high-units-bytes 0B} + + + + diff --git a/proto/topics/topic_service_messages_v1.pb.go b/proto/topics/topic_service_messages_v1.pb.go new file mode 100644 index 00000000..2a3f3aba --- /dev/null +++ b/proto/topics/topic_service_messages_v1.pb.go @@ -0,0 +1,3130 @@ +// +// Copyright (c) 2020, 2025, Oracle and/or its affiliates. +// +// Licensed under the Universal Permissive License v 1.0 as shown at +// https://oss.oracle.com/licenses/upl. + +// ----------------------------------------------------------------- +// Messages used by the Coherence gRPC NamedTopic Service. +// +// NOTE: If you add a new request message to this message the current +// protocol version in com.oracle.coherence.grpc.NamedTopicProtocol must +// be increased. This only needs to be done once for any given Coherence +// release. +// ----------------------------------------------------------------- + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v3.19.2 +// source: topic_service_messages_v1.proto + +package topics + +import ( + v1 "github.com/oracle/coherence-go-client/v2/proto/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + _ "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + _ "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// An enum representing the types of request for a Named Topic Service proxy +// +// NOTE: The index numbers for the enum elements MUST NOT BE CHANGED as +// that would break backwards compatibility. Only new index numbers can +// be added. +type TopicServiceRequestType int32 + +const ( + // An unknown message. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + TopicServiceRequestType_RequestUnknown TopicServiceRequestType = 0 + // Called to ensure a topic. + // Must be the first message called prior to any other topic requests. + // The message field must be an EnsureTopicRequest. + // The response will contain the Topic Id and an empty response field. + TopicServiceRequestType_EnsureTopic TopicServiceRequestType = 1 + // Destroy the specified topic. + // A destroy topic message must be sent with a proxy identifier of zero as it + // is targeted at the topic service proxy. + // The message field should be set to a StringValue containing the name of the topic to destroy. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroyTopic TopicServiceRequestType = 2 + // Called to get the channel count for a topic. + // This message can be sent without ensuring the topic first and does not require + // a topic id to be set. If the topic id is set then that will be used to identify + // the topic to use, otherwise the topic name should be in the StringValue message. + // If no topic id is set, the message field is a StringValue which is the name of the + // topic to get the channel count for. + // The response will be an Int32Value containing the channel count + TopicServiceRequestType_GetChannelCount TopicServiceRequestType = 3 + // Get the durable subscriber groups for a topic + // This message can be sent without ensuring the topic first and does not require + // a topic id to be set. If the topic id is set then that will be used to identify + // the topic to use, otherwise the topic name should be in the StringValue message. + // If no topic id is set, the message field is a StringValue which is the name of the + // topic to get the subscriber groups for. + // The response will be a CollectionOfString message containing the names of the + // subscriber groups. + TopicServiceRequestType_GetSubscriberGroups TopicServiceRequestType = 4 + // Ensure that a topic has a specified number of channels + // The message field should be an EnsureChannelCount message + // The response wil be an Int32Value containing the topic channel count + TopicServiceRequestType_EnsureChannelCount TopicServiceRequestType = 5 + // Ensure a subscriber group exists for a topic. + // The message field should be an EnsureSubscriberGroupRequest message + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_EnsureSubscriberGroup TopicServiceRequestType = 6 + // Ensure a subscriber group exists for a topic. + // The message field should be an StringValue containing the name of the + // subscriber group to be destroyed + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroySubscriberGroup TopicServiceRequestType = 7 + // Get a count of the remaining messages in a topic for a subscriber group. + // The message field should be a GetRemainingMessagesRequest + // The response will be an Int32Value containing the count of remaining messages. + TopicServiceRequestType_GetRemainingMessages TopicServiceRequestType = 8 + // Get the tail positions for a topic + // The response will be a MapOfChannelAndPosition with a position for each channel. + TopicServiceRequestType_GetTails TopicServiceRequestType = 9 + // Called to ensure a publisher. + // Must be the first message called prior to any other publisher requests. + // The message field must be an EnsurePublisherRequest. + // The response will contain the Publisher Id and an EnsurePublisherResponse response message. + TopicServiceRequestType_EnsurePublisher TopicServiceRequestType = 10 + // Destroy the specified publisher. + // The message field must be set to an Int32Value containing the publisher identifier + // returned by the original ensure publisher request. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroyPublisher TopicServiceRequestType = 11 + // Publish values to a topic + // The message field must be a PublishRequest + // The response will be a PublishResult message + TopicServiceRequestType_Publish TopicServiceRequestType = 12 + // Called to ensure a subscriber. + // Must be the first message called prior to any other subscriber requests. + // The message field must be an EnsureSubscriberRequest. + // The response will contain the Subscriber Id and an EnsureSubscriberResponse + // message in the response field. + TopicServiceRequestType_EnsureSubscriber TopicServiceRequestType = 13 + // Destroy the specified subscriber. + // The message field must be set to an Int32Value containing the subscriber identifier + // returned by the original ensure subscriber request. + // The response will just be a Complete message corresponding to the request id. + TopicServiceRequestType_DestroySubscriber TopicServiceRequestType = 14 + // Initialize a subscriber connection + // The message field should be an InitializeSubscriptionRequest message. + // The response will be a InitializeSubscriptionResponse message. + TopicServiceRequestType_InitializeSubscription TopicServiceRequestType = 15 + // Ensure a subscriber has a subscription to the topic. + // The message field should be an EnsureSubscriptionRequest message. + // The response will be a BoolValue indicating whether the subscription exists + TopicServiceRequestType_EnsureSubscription TopicServiceRequestType = 16 + // Get the head positions for a subscriber + // The message body should be a CollectionOfInt32 specifying the channels to + // get the head positions for. + // The response will be a MapOfChannelAndPosition with a position for each channel. + TopicServiceRequestType_GetSubscriberHeads TopicServiceRequestType = 17 + // Get the last committed positions for a subscriber. + // The message field should be empty. + // The response will be a MapOfChannelAndPosition with a last committed position for each channel. + TopicServiceRequestType_GetLastCommited TopicServiceRequestType = 18 + // Obtain the channels that are owned by a subscriber. + // The message field should be empty. + // The response will be a CollectionOfInt32 containing the owned channel identifiers. + TopicServiceRequestType_GetOwnedChannels TopicServiceRequestType = 19 + // Send a heartbeat message for a subscriber. + // The message contains a BoolValue indicating whether the heartbeat should + // be sent asynchronously. + TopicServiceRequestType_SubscriberHeartbeat TopicServiceRequestType = 20 + // Determine whether a position has been committed by a subscriber. + // The message contains a ChannelAndPosition value. + // The response will be a BoolValue indicating whether the position is committed. + TopicServiceRequestType_IsPositionCommitted TopicServiceRequestType = 21 + // Peek at a value in a position. + // The message contains a ChannelAndPosition value. + // The response will be a TopicElement with the value from the position + TopicServiceRequestType_PeekAtPosition TopicServiceRequestType = 22 + // Receive values from a topic. + // The message should be a ReceiveRequest. + // The response will be a ReceiveResponse. + TopicServiceRequestType_Receive TopicServiceRequestType = 23 + // Seek a subscriber to a new position. + // The message should be a SeekRequest. + // The response will be a SeekResult. + TopicServiceRequestType_SeekSubscriber TopicServiceRequestType = 24 + // Commit a channel and position + // The message should be a ChannelAndPosition + // The response will be a CommitResponse + TopicServiceRequestType_CommitPosition TopicServiceRequestType = 25 +) + +// Enum value maps for TopicServiceRequestType. +var ( + TopicServiceRequestType_name = map[int32]string{ + 0: "RequestUnknown", + 1: "EnsureTopic", + 2: "DestroyTopic", + 3: "GetChannelCount", + 4: "GetSubscriberGroups", + 5: "EnsureChannelCount", + 6: "EnsureSubscriberGroup", + 7: "DestroySubscriberGroup", + 8: "GetRemainingMessages", + 9: "GetTails", + 10: "EnsurePublisher", + 11: "DestroyPublisher", + 12: "Publish", + 13: "EnsureSubscriber", + 14: "DestroySubscriber", + 15: "InitializeSubscription", + 16: "EnsureSubscription", + 17: "GetSubscriberHeads", + 18: "GetLastCommited", + 19: "GetOwnedChannels", + 20: "SubscriberHeartbeat", + 21: "IsPositionCommitted", + 22: "PeekAtPosition", + 23: "Receive", + 24: "SeekSubscriber", + 25: "CommitPosition", + } + TopicServiceRequestType_value = map[string]int32{ + "RequestUnknown": 0, + "EnsureTopic": 1, + "DestroyTopic": 2, + "GetChannelCount": 3, + "GetSubscriberGroups": 4, + "EnsureChannelCount": 5, + "EnsureSubscriberGroup": 6, + "DestroySubscriberGroup": 7, + "GetRemainingMessages": 8, + "GetTails": 9, + "EnsurePublisher": 10, + "DestroyPublisher": 11, + "Publish": 12, + "EnsureSubscriber": 13, + "DestroySubscriber": 14, + "InitializeSubscription": 15, + "EnsureSubscription": 16, + "GetSubscriberHeads": 17, + "GetLastCommited": 18, + "GetOwnedChannels": 19, + "SubscriberHeartbeat": 20, + "IsPositionCommitted": 21, + "PeekAtPosition": 22, + "Receive": 23, + "SeekSubscriber": 24, + "CommitPosition": 25, + } +) + +func (x TopicServiceRequestType) Enum() *TopicServiceRequestType { + p := new(TopicServiceRequestType) + *p = x + return p +} + +func (x TopicServiceRequestType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TopicServiceRequestType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[0].Descriptor() +} + +func (TopicServiceRequestType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[0] +} + +func (x TopicServiceRequestType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TopicServiceRequestType.Descriptor instead. +func (TopicServiceRequestType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{0} +} + +// An enum representing different types of response. +// +// NOTE: The index numbers for the enum elements MUST NOT BE CHANGED as +// that would break backwards compatibility. Only new index numbers can +// be added. +type ResponseType int32 + +const ( + // The response is a message. + ResponseType_Message ResponseType = 0 + // The response is a map event. + ResponseType_Event ResponseType = 1 +) + +// Enum value maps for ResponseType. +var ( + ResponseType_name = map[int32]string{ + 0: "Message", + 1: "Event", + } + ResponseType_value = map[string]int32{ + "Message": 0, + "Event": 1, + } +) + +func (x ResponseType) Enum() *ResponseType { + p := new(ResponseType) + *p = x + return p +} + +func (x ResponseType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResponseType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[1].Descriptor() +} + +func (ResponseType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[1] +} + +func (x ResponseType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResponseType.Descriptor instead. +func (ResponseType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{1} +} + +type TopicEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + TopicEventType_EventUnknown TopicEventType = 0 + // The topic has been destroyed + TopicEventType_TopicDestroyed TopicEventType = 1 +) + +// Enum value maps for TopicEventType. +var ( + TopicEventType_name = map[int32]string{ + 0: "EventUnknown", + 1: "TopicDestroyed", + } + TopicEventType_value = map[string]int32{ + "EventUnknown": 0, + "TopicDestroyed": 1, + } +) + +func (x TopicEventType) Enum() *TopicEventType { + p := new(TopicEventType) + *p = x + return p +} + +func (x TopicEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TopicEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[2].Descriptor() +} + +func (TopicEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[2] +} + +func (x TopicEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TopicEventType.Descriptor instead. +func (TopicEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{2} +} + +// The types for a publisher event. +type PublisherEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + PublisherEventType_PublisherEventUnknown PublisherEventType = 0 + // The publisher has connected. + PublisherEventType_PublisherConnected PublisherEventType = 1 + // The publisher has disconnected. + PublisherEventType_PublisherDisconnected PublisherEventType = 2 + // A previously full topic now has space + PublisherEventType_PublisherChannelsFreed PublisherEventType = 3 + // The topic the publisher publishes to has been destroyed. + PublisherEventType_PublisherDestroyed PublisherEventType = 4 + // The topic the publisher publishes to has been released. + PublisherEventType_PublisherReleased PublisherEventType = 5 +) + +// Enum value maps for PublisherEventType. +var ( + PublisherEventType_name = map[int32]string{ + 0: "PublisherEventUnknown", + 1: "PublisherConnected", + 2: "PublisherDisconnected", + 3: "PublisherChannelsFreed", + 4: "PublisherDestroyed", + 5: "PublisherReleased", + } + PublisherEventType_value = map[string]int32{ + "PublisherEventUnknown": 0, + "PublisherConnected": 1, + "PublisherDisconnected": 2, + "PublisherChannelsFreed": 3, + "PublisherDestroyed": 4, + "PublisherReleased": 5, + } +) + +func (x PublisherEventType) Enum() *PublisherEventType { + p := new(PublisherEventType) + *p = x + return p +} + +func (x PublisherEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PublisherEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[3].Descriptor() +} + +func (PublisherEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[3] +} + +func (x PublisherEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PublisherEventType.Descriptor instead. +func (PublisherEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{3} +} + +type PublishStatus int32 + +const ( + // The offer invocation was successful and all elements were + // accepted into the page. + PublishStatus_Success PublishStatus = 0 + // The offer invocation was unsuccessful as the topic was full. + // The offer may have been partially successful if multiple elements + // had been offered. + // The publisher should wait for a PublisherEvent of type + PublishStatus_TopicFull PublishStatus = 1 +) + +// Enum value maps for PublishStatus. +var ( + PublishStatus_name = map[int32]string{ + 0: "Success", + 1: "TopicFull", + } + PublishStatus_value = map[string]int32{ + "Success": 0, + "TopicFull": 1, + } +) + +func (x PublishStatus) Enum() *PublishStatus { + p := new(PublishStatus) + *p = x + return p +} + +func (x PublishStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PublishStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[4].Descriptor() +} + +func (PublishStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[4] +} + +func (x PublishStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PublishStatus.Descriptor instead. +func (PublishStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +type SubscriberEventType int32 + +const ( + // An unknown type. + // This request type is not used, it is here as enums must have a zero value, + // but we need to know the difference between a zero value and the field being + // incorrectly set. + SubscriberEventType_SubscriberEventUnknown SubscriberEventType = 0 + // The event indicates the subscriber group was destroyed. + SubscriberEventType_SubscriberGroupDestroyed SubscriberEventType = 1 + // The event is a channel allocation event. + SubscriberEventType_SubscriberChannelAllocation SubscriberEventType = 2 + // The event is a channels lost event. + SubscriberEventType_SubscriberChannelsLost SubscriberEventType = 3 + // The event is a channel populated event. + SubscriberEventType_SubscriberChannelPopulated SubscriberEventType = 4 + // The head position of a channel has changed + SubscriberEventType_SubscriberChannelHead SubscriberEventType = 5 + // The event is an unsubscribed event. + SubscriberEventType_SubscriberUnsubscribed SubscriberEventType = 6 + // The parent topic was destroyed. + SubscriberEventType_SubscriberDestroyed SubscriberEventType = 7 + // The parent topic was released. + SubscriberEventType_SubscriberReleased SubscriberEventType = 8 + // The subscriber was disconnected. + SubscriberEventType_SubscriberDisconnected SubscriberEventType = 9 +) + +// Enum value maps for SubscriberEventType. +var ( + SubscriberEventType_name = map[int32]string{ + 0: "SubscriberEventUnknown", + 1: "SubscriberGroupDestroyed", + 2: "SubscriberChannelAllocation", + 3: "SubscriberChannelsLost", + 4: "SubscriberChannelPopulated", + 5: "SubscriberChannelHead", + 6: "SubscriberUnsubscribed", + 7: "SubscriberDestroyed", + 8: "SubscriberReleased", + 9: "SubscriberDisconnected", + } + SubscriberEventType_value = map[string]int32{ + "SubscriberEventUnknown": 0, + "SubscriberGroupDestroyed": 1, + "SubscriberChannelAllocation": 2, + "SubscriberChannelsLost": 3, + "SubscriberChannelPopulated": 4, + "SubscriberChannelHead": 5, + "SubscriberUnsubscribed": 6, + "SubscriberDestroyed": 7, + "SubscriberReleased": 8, + "SubscriberDisconnected": 9, + } +) + +func (x SubscriberEventType) Enum() *SubscriberEventType { + p := new(SubscriberEventType) + *p = x + return p +} + +func (x SubscriberEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SubscriberEventType) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[5].Descriptor() +} + +func (SubscriberEventType) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[5] +} + +func (x SubscriberEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SubscriberEventType.Descriptor instead. +func (SubscriberEventType) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{5} +} + +// The status of a receive response. +type ReceiveStatus int32 + +const ( + // The receive request was successful. + ReceiveStatus_ReceiveSuccess ReceiveStatus = 0 + // The channel was exhausted. + ReceiveStatus_ChannelExhausted ReceiveStatus = 1 + // The channel was not allocated to the subscriber. + ReceiveStatus_ChannelNotAllocatedChannel ReceiveStatus = 2 + // The server did not recognise the subscriber + ReceiveStatus_UnknownSubscriber ReceiveStatus = 3 +) + +// Enum value maps for ReceiveStatus. +var ( + ReceiveStatus_name = map[int32]string{ + 0: "ReceiveSuccess", + 1: "ChannelExhausted", + 2: "ChannelNotAllocatedChannel", + 3: "UnknownSubscriber", + } + ReceiveStatus_value = map[string]int32{ + "ReceiveSuccess": 0, + "ChannelExhausted": 1, + "ChannelNotAllocatedChannel": 2, + "UnknownSubscriber": 3, + } +) + +func (x ReceiveStatus) Enum() *ReceiveStatus { + p := new(ReceiveStatus) + *p = x + return p +} + +func (x ReceiveStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReceiveStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[6].Descriptor() +} + +func (ReceiveStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[6] +} + +func (x ReceiveStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReceiveStatus.Descriptor instead. +func (ReceiveStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{6} +} + +// The different result statuses for a commit request. +type CommitResponseStatus int32 + +const ( + // The position was successfully committed. + CommitResponseStatus_Committed CommitResponseStatus = 0 + // The position was already committed. + // Typically, this is caused by a commit of a higher position in the channel + // already being processed. + CommitResponseStatus_AlreadyCommitted CommitResponseStatus = 1 + // The commit request was rejected. + CommitResponseStatus_Rejected CommitResponseStatus = 2 + // The position was successfully committed but the committing subscriber + // does not own the committed channel. + CommitResponseStatus_Unowned CommitResponseStatus = 3 + // A commit request was made, but there was no position to be committed. + CommitResponseStatus_NothingToCommit CommitResponseStatus = 4 +) + +// Enum value maps for CommitResponseStatus. +var ( + CommitResponseStatus_name = map[int32]string{ + 0: "Committed", + 1: "AlreadyCommitted", + 2: "Rejected", + 3: "Unowned", + 4: "NothingToCommit", + } + CommitResponseStatus_value = map[string]int32{ + "Committed": 0, + "AlreadyCommitted": 1, + "Rejected": 2, + "Unowned": 3, + "NothingToCommit": 4, + } +) + +func (x CommitResponseStatus) Enum() *CommitResponseStatus { + p := new(CommitResponseStatus) + *p = x + return p +} + +func (x CommitResponseStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CommitResponseStatus) Descriptor() protoreflect.EnumDescriptor { + return file_topic_service_messages_v1_proto_enumTypes[7].Descriptor() +} + +func (CommitResponseStatus) Type() protoreflect.EnumType { + return &file_topic_service_messages_v1_proto_enumTypes[7] +} + +func (x CommitResponseStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CommitResponseStatus.Descriptor instead. +func (CommitResponseStatus) EnumDescriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{7} +} + +// A request to perform an operation on a remote NamedTopic. +type TopicServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the request + Type TopicServiceRequestType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.TopicServiceRequestType" json:"type,omitempty"` + // The topic identifier for the request. + // The identifier must be the same value returned by the initial ensure request. + // This is optional only for EnsureTopic as this cannot have a topic identifier + ProxyId *int32 `protobuf:"varint,2,opt,name=proxyId,proto3,oneof" json:"proxyId,omitempty"` + // The actual request message, this is optional because some messages do not require + // a message body, for example topic.size() + // The actual request message should be packed inside an Any message and set in this field. + // The proxy will know which message type to expect here based on the "type" field's value. + Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3,oneof" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicServiceRequest) Reset() { + *x = TopicServiceRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicServiceRequest) ProtoMessage() {} + +func (x *TopicServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicServiceRequest.ProtoReflect.Descriptor instead. +func (*TopicServiceRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{0} +} + +func (x *TopicServiceRequest) GetType() TopicServiceRequestType { + if x != nil { + return x.Type + } + return TopicServiceRequestType_RequestUnknown +} + +func (x *TopicServiceRequest) GetProxyId() int32 { + if x != nil && x.ProxyId != nil { + return *x.ProxyId + } + return 0 +} + +func (x *TopicServiceRequest) GetMessage() *anypb.Any { + if x != nil { + return x.Message + } + return nil +} + +// A response message from a Named Topic Service proxy. +// +// NOTE: If you add a new request message to this message the protocol +// version in com.oracle.coherence.grpc.NamedTopicProtocol must be +// increased. This only needs to be done once for any given Coherence +// release. +type TopicServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The proxy identifier for the response + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // An enum representing different response types. + // The type of the request. + Type ResponseType `protobuf:"varint,2,opt,name=type,proto3,enum=coherence.topic.v1.ResponseType" json:"type,omitempty"` + // The response can contain one of a number of response types + // The sender of the corresponding request should know which + // response type it expects + Message *anypb.Any `protobuf:"bytes,3,opt,name=message,proto3,oneof" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicServiceResponse) Reset() { + *x = TopicServiceResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicServiceResponse) ProtoMessage() {} + +func (x *TopicServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicServiceResponse.ProtoReflect.Descriptor instead. +func (*TopicServiceResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{1} +} + +func (x *TopicServiceResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *TopicServiceResponse) GetType() ResponseType { + if x != nil { + return x.Type + } + return ResponseType_Message +} + +func (x *TopicServiceResponse) GetMessage() *anypb.Any { + if x != nil { + return x.Message + } + return nil +} + +// A request to ensure a specific topic. +type EnsureTopicRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureTopicRequest) Reset() { + *x = EnsureTopicRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureTopicRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureTopicRequest) ProtoMessage() {} + +func (x *EnsureTopicRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureTopicRequest.ProtoReflect.Descriptor instead. +func (*EnsureTopicRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{2} +} + +func (x *EnsureTopicRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +// An event to indicate the state of a NamedTopic has changed. +type NamedTopicEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type TopicEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.TopicEventType" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NamedTopicEvent) Reset() { + *x = NamedTopicEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NamedTopicEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamedTopicEvent) ProtoMessage() {} + +func (x *NamedTopicEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NamedTopicEvent.ProtoReflect.Descriptor instead. +func (*NamedTopicEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{3} +} + +func (x *NamedTopicEvent) GetType() TopicEventType { + if x != nil { + return x.Type + } + return TopicEventType_EventUnknown +} + +// Ensure that a topic has a specified number of channels. +type EnsureChannelCountRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the topic if this message is sent without a topic id + Topic *string `protobuf:"bytes,1,opt,name=topic,proto3,oneof" json:"topic,omitempty"` + // the required number of channels + RequiredCount int32 `protobuf:"varint,2,opt,name=requiredCount,proto3" json:"requiredCount,omitempty"` + // the number of channels to create if the actual count is less + // than the requiredCount + ChannelCount *int32 `protobuf:"varint,3,opt,name=channelCount,proto3,oneof" json:"channelCount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureChannelCountRequest) Reset() { + *x = EnsureChannelCountRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureChannelCountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureChannelCountRequest) ProtoMessage() {} + +func (x *EnsureChannelCountRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureChannelCountRequest.ProtoReflect.Descriptor instead. +func (*EnsureChannelCountRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +func (x *EnsureChannelCountRequest) GetTopic() string { + if x != nil && x.Topic != nil { + return *x.Topic + } + return "" +} + +func (x *EnsureChannelCountRequest) GetRequiredCount() int32 { + if x != nil { + return x.RequiredCount + } + return 0 +} + +func (x *EnsureChannelCountRequest) GetChannelCount() int32 { + if x != nil && x.ChannelCount != nil { + return *x.ChannelCount + } + return 0 +} + +// A request to ensure a subscriber group exists for a topic. +type EnsureSubscriberGroupRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the subscriber group + SubscriberGroup string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3" json:"subscriberGroup,omitempty"` + // an optional Filter to filter received messages + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // an optional ValueExtractor to convert received messages + Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberGroupRequest) Reset() { + *x = EnsureSubscriberGroupRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberGroupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberGroupRequest) ProtoMessage() {} + +func (x *EnsureSubscriberGroupRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberGroupRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberGroupRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{5} +} + +func (x *EnsureSubscriberGroupRequest) GetSubscriberGroup() string { + if x != nil { + return x.SubscriberGroup + } + return "" +} + +func (x *EnsureSubscriberGroupRequest) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +func (x *EnsureSubscriberGroupRequest) GetExtractor() []byte { + if x != nil { + return x.Extractor + } + return nil +} + +// Get a count of the remaining messages in a topic for a subscriber group. +type GetRemainingMessagesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the name of the topic if this message is sent without a topic id + Topic *string `protobuf:"bytes,1,opt,name=topic,proto3,oneof" json:"topic,omitempty"` + // The subscriber group to obtain the remaining message counts for + SubscriberGroup string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3" json:"subscriberGroup,omitempty"` + // The channels to obtain the remaining message count for. + // An empty channel set will return all channels. + Channels []int32 `protobuf:"varint,3,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRemainingMessagesRequest) Reset() { + *x = GetRemainingMessagesRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemainingMessagesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemainingMessagesRequest) ProtoMessage() {} + +func (x *GetRemainingMessagesRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRemainingMessagesRequest.ProtoReflect.Descriptor instead. +func (*GetRemainingMessagesRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{6} +} + +func (x *GetRemainingMessagesRequest) GetTopic() string { + if x != nil && x.Topic != nil { + return *x.Topic + } + return "" +} + +func (x *GetRemainingMessagesRequest) GetSubscriberGroup() string { + if x != nil { + return x.SubscriberGroup + } + return "" +} + +func (x *GetRemainingMessagesRequest) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// A request to ensure a specific publisher. +type EnsurePublisherRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // The number of channels the publisher requires + ChannelCount int32 `protobuf:"varint,2,opt,name=channelCount,proto3" json:"channelCount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsurePublisherRequest) Reset() { + *x = EnsurePublisherRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsurePublisherRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsurePublisherRequest) ProtoMessage() {} + +func (x *EnsurePublisherRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsurePublisherRequest.ProtoReflect.Descriptor instead. +func (*EnsurePublisherRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{7} +} + +func (x *EnsurePublisherRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *EnsurePublisherRequest) GetChannelCount() int32 { + if x != nil { + return x.ChannelCount + } + return 0 +} + +// The response message sent as a result of an EnsurePublisher request. +type EnsurePublisherResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The publisher proxy identifier + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // The identifier of the publisher + PublisherId int64 `protobuf:"varint,2,opt,name=publisherId,proto3" json:"publisherId,omitempty"` + // The number of channels the topic has. + ChannelCount int32 `protobuf:"varint,3,opt,name=channelCount,proto3" json:"channelCount,omitempty"` + // The maximum batch size before the publisher is throttled + MaxBatchSize int64 `protobuf:"varint,4,opt,name=maxBatchSize,proto3" json:"maxBatchSize,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsurePublisherResponse) Reset() { + *x = EnsurePublisherResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsurePublisherResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsurePublisherResponse) ProtoMessage() {} + +func (x *EnsurePublisherResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsurePublisherResponse.ProtoReflect.Descriptor instead. +func (*EnsurePublisherResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{8} +} + +func (x *EnsurePublisherResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *EnsurePublisherResponse) GetPublisherId() int64 { + if x != nil { + return x.PublisherId + } + return 0 +} + +func (x *EnsurePublisherResponse) GetChannelCount() int32 { + if x != nil { + return x.ChannelCount + } + return 0 +} + +func (x *EnsurePublisherResponse) GetMaxBatchSize() int64 { + if x != nil { + return x.MaxBatchSize + } + return 0 +} + +// An event to indicate the state of a NamedTopic Publisher has changed. +type PublisherEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type PublisherEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.PublisherEventType" json:"type,omitempty"` + // The channels the event relates to. + Channels []int32 `protobuf:"varint,2,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublisherEvent) Reset() { + *x = PublisherEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublisherEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublisherEvent) ProtoMessage() {} + +func (x *PublisherEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublisherEvent.ProtoReflect.Descriptor instead. +func (*PublisherEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{9} +} + +func (x *PublisherEvent) GetType() PublisherEventType { + if x != nil { + return x.Type + } + return PublisherEventType_PublisherEventUnknown +} + +func (x *PublisherEvent) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// A request to publish values to a channel in a topic +// using a previously ensured publisher. +type PublishRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel to publish to + Channel int32 `protobuf:"varint,2,opt,name=channel,proto3" json:"channel,omitempty"` + // The serialized values to publish. + Values [][]byte `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` + // The identifier used to register for notifications + // if the topic is full. + // This is used by the Coherence Java client and can be safely + // ignored in other implementations. + NotificationIdentifier *int32 `protobuf:"varint,4,opt,name=notificationIdentifier,proto3,oneof" json:"notificationIdentifier,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishRequest) Reset() { + *x = PublishRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishRequest) ProtoMessage() {} + +func (x *PublishRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishRequest.ProtoReflect.Descriptor instead. +func (*PublishRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{10} +} + +func (x *PublishRequest) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *PublishRequest) GetValues() [][]byte { + if x != nil { + return x.Values + } + return nil +} + +func (x *PublishRequest) GetNotificationIdentifier() int32 { + if x != nil && x.NotificationIdentifier != nil { + return *x.NotificationIdentifier + } + return 0 +} + +// The status of a published value. +type PublishedValueStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to State: + // + // *PublishedValueStatus_Position + // *PublishedValueStatus_Error + State isPublishedValueStatus_State `protobuf_oneof:"state"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishedValueStatus) Reset() { + *x = PublishedValueStatus{} + mi := &file_topic_service_messages_v1_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishedValueStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishedValueStatus) ProtoMessage() {} + +func (x *PublishedValueStatus) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishedValueStatus.ProtoReflect.Descriptor instead. +func (*PublishedValueStatus) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{11} +} + +func (x *PublishedValueStatus) GetState() isPublishedValueStatus_State { + if x != nil { + return x.State + } + return nil +} + +func (x *PublishedValueStatus) GetPosition() *TopicPosition { + if x != nil { + if x, ok := x.State.(*PublishedValueStatus_Position); ok { + return x.Position + } + } + return nil +} + +func (x *PublishedValueStatus) GetError() *v1.ErrorMessage { + if x != nil { + if x, ok := x.State.(*PublishedValueStatus_Error); ok { + return x.Error + } + } + return nil +} + +type isPublishedValueStatus_State interface { + isPublishedValueStatus_State() +} + +type PublishedValueStatus_Position struct { + // An opaque representation of the position the element was published to + Position *TopicPosition `protobuf:"bytes,1,opt,name=position,proto3,oneof"` +} + +type PublishedValueStatus_Error struct { + // Any error that may have occurred, in which case the value was not published. + Error *v1.ErrorMessage `protobuf:"bytes,2,opt,name=error,proto3,oneof"` +} + +func (*PublishedValueStatus_Position) isPublishedValueStatus_State() {} + +func (*PublishedValueStatus_Error) isPublishedValueStatus_State() {} + +// The result of publishing values to a topic. +type PublishResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel published to. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The result status. + Status PublishStatus `protobuf:"varint,3,opt,name=status,proto3,enum=coherence.topic.v1.PublishStatus" json:"status,omitempty"` + // The number of values that were successfully published. + AcceptedCount int32 `protobuf:"varint,5,opt,name=acceptedCount,proto3" json:"acceptedCount,omitempty"` + // The remaining capacity. + RemainingCapacity int32 `protobuf:"varint,6,opt,name=remainingCapacity,proto3" json:"remainingCapacity,omitempty"` + // A status for each of the published values. + ValueStatus []*PublishedValueStatus `protobuf:"bytes,7,rep,name=valueStatus,proto3" json:"valueStatus,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PublishResult) Reset() { + *x = PublishResult{} + mi := &file_topic_service_messages_v1_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublishResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishResult) ProtoMessage() {} + +func (x *PublishResult) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishResult.ProtoReflect.Descriptor instead. +func (*PublishResult) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{12} +} + +func (x *PublishResult) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *PublishResult) GetStatus() PublishStatus { + if x != nil { + return x.Status + } + return PublishStatus_Success +} + +func (x *PublishResult) GetAcceptedCount() int32 { + if x != nil { + return x.AcceptedCount + } + return 0 +} + +func (x *PublishResult) GetRemainingCapacity() int32 { + if x != nil { + return x.RemainingCapacity + } + return 0 +} + +func (x *PublishResult) GetValueStatus() []*PublishedValueStatus { + if x != nil { + return x.ValueStatus + } + return nil +} + +// A position within a paged topic. +type PagedPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The page identifier. + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + // The offset within a page + Offset int32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PagedPosition) Reset() { + *x = PagedPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PagedPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PagedPosition) ProtoMessage() {} + +func (x *PagedPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PagedPosition.ProtoReflect.Descriptor instead. +func (*PagedPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{13} +} + +func (x *PagedPosition) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *PagedPosition) GetOffset() int32 { + if x != nil { + return x.Offset + } + return 0 +} + +// An opaque topic position +type TopicPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Position *anypb.Any `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicPosition) Reset() { + *x = TopicPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicPosition) ProtoMessage() {} + +func (x *TopicPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicPosition.ProtoReflect.Descriptor instead. +func (*TopicPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{14} +} + +func (x *TopicPosition) GetPosition() *anypb.Any { + if x != nil { + return x.Position + } + return nil +} + +// The response to an EnsureSubscriberRequest +type EnsureSubscriberResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The publisher proxy identifier + ProxyId int32 `protobuf:"varint,1,opt,name=proxyId,proto3" json:"proxyId,omitempty"` + // The unique server side subscriber identifier. + SubscriberId *SubscriberId `protobuf:"bytes,2,opt,name=subscriberId,proto3" json:"subscriberId,omitempty"` + // The subscribers group identifier + GroupId *SubscriberGroupId `protobuf:"bytes,3,opt,name=groupId,proto3" json:"groupId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberResponse) Reset() { + *x = EnsureSubscriberResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberResponse) ProtoMessage() {} + +func (x *EnsureSubscriberResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberResponse.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{15} +} + +func (x *EnsureSubscriberResponse) GetProxyId() int32 { + if x != nil { + return x.ProxyId + } + return 0 +} + +func (x *EnsureSubscriberResponse) GetSubscriberId() *SubscriberId { + if x != nil { + return x.SubscriberId + } + return nil +} + +func (x *EnsureSubscriberResponse) GetGroupId() *SubscriberGroupId { + if x != nil { + return x.GroupId + } + return nil +} + +// A request to ensure a specific subscriber. +type EnsureSubscriberRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // the optional name of the subscriber group + SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` + // an optional Filter to filter received messages + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // an optional ValueExtractor to convert received messages + Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` + // True to return an empty value if the topic is empty + CompleteOnEmpty bool `protobuf:"varint,5,opt,name=completeOnEmpty,proto3" json:"completeOnEmpty,omitempty"` + // The channels to allocate to this subscriber (invalid channels will be ignored) + Channels []int32 `protobuf:"varint,6,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriberRequest) Reset() { + *x = EnsureSubscriberRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriberRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriberRequest) ProtoMessage() {} + +func (x *EnsureSubscriberRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriberRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriberRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{16} +} + +func (x *EnsureSubscriberRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *EnsureSubscriberRequest) GetSubscriberGroup() string { + if x != nil && x.SubscriberGroup != nil { + return *x.SubscriberGroup + } + return "" +} + +func (x *EnsureSubscriberRequest) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +func (x *EnsureSubscriberRequest) GetExtractor() []byte { + if x != nil { + return x.Extractor + } + return nil +} + +func (x *EnsureSubscriberRequest) GetCompleteOnEmpty() bool { + if x != nil { + return x.CompleteOnEmpty + } + return false +} + +func (x *EnsureSubscriberRequest) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// An event to indicate the state of a NamedTopic Subscriber has changed. +type SubscriberEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The type of the event. + Type SubscriberEventType `protobuf:"varint,1,opt,name=type,proto3,enum=coherence.topic.v1.SubscriberEventType" json:"type,omitempty"` + // The channels associated with the event + Channels []int32 `protobuf:"varint,2,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberEvent) Reset() { + *x = SubscriberEvent{} + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberEvent) ProtoMessage() {} + +func (x *SubscriberEvent) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberEvent.ProtoReflect.Descriptor instead. +func (*SubscriberEvent) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{17} +} + +func (x *SubscriberEvent) GetType() SubscriberEventType { + if x != nil { + return x.Type + } + return SubscriberEventType_SubscriberEventUnknown +} + +func (x *SubscriberEvent) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + +// The unique identifier for a subscriber. +type SubscriberId struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The subscriber identifier. + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // The owning member UUID. + Uuid []byte `protobuf:"bytes,4,opt,name=uuid,proto3" json:"uuid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberId) Reset() { + *x = SubscriberId{} + mi := &file_topic_service_messages_v1_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberId) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberId) ProtoMessage() {} + +func (x *SubscriberId) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberId.ProtoReflect.Descriptor instead. +func (*SubscriberId) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{18} +} + +func (x *SubscriberId) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SubscriberId) GetUuid() []byte { + if x != nil { + return x.Uuid + } + return nil +} + +// An identifier for a subscriber group +type SubscriberGroupId struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The subscriber group name + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The subscriber group identifier + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriberGroupId) Reset() { + *x = SubscriberGroupId{} + mi := &file_topic_service_messages_v1_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriberGroupId) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriberGroupId) ProtoMessage() {} + +func (x *SubscriberGroupId) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriberGroupId.ProtoReflect.Descriptor instead. +func (*SubscriberGroupId) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{19} +} + +func (x *SubscriberGroupId) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SubscriberGroupId) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +// Initialize the subscriber connection. +type InitializeSubscriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A flag to indicate if the subscriber was initially disconnected. + Disconnected bool `protobuf:"varint,1,opt,name=disconnected,proto3" json:"disconnected,omitempty"` + // This is a reconnection of an existing subscriber + Reconnect bool `protobuf:"varint,2,opt,name=reconnect,proto3" json:"reconnect,omitempty"` + // A flag to indicate that the reconnect logic should force a reconnect + // request even if the subscriber is in the config map + ForceReconnect bool `protobuf:"varint,3,opt,name=forceReconnect,proto3" json:"forceReconnect,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InitializeSubscriptionRequest) Reset() { + *x = InitializeSubscriptionRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InitializeSubscriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeSubscriptionRequest) ProtoMessage() {} + +func (x *InitializeSubscriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeSubscriptionRequest.ProtoReflect.Descriptor instead. +func (*InitializeSubscriptionRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{20} +} + +func (x *InitializeSubscriptionRequest) GetDisconnected() bool { + if x != nil { + return x.Disconnected + } + return false +} + +func (x *InitializeSubscriptionRequest) GetReconnect() bool { + if x != nil { + return x.Reconnect + } + return false +} + +func (x *InitializeSubscriptionRequest) GetForceReconnect() bool { + if x != nil { + return x.ForceReconnect + } + return false +} + +// A response to an initialize subscriber request. +type InitializeSubscriptionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The identifier for the subscribers current subscription. + SubscriptionId int64 `protobuf:"varint,1,opt,name=subscriptionId,proto3" json:"subscriptionId,omitempty"` + // The subscribers connection timestamp + Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // The head positions of the topic channels + Heads []*TopicPosition `protobuf:"bytes,3,rep,name=heads,proto3" json:"heads,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InitializeSubscriptionResponse) Reset() { + *x = InitializeSubscriptionResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InitializeSubscriptionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeSubscriptionResponse) ProtoMessage() {} + +func (x *InitializeSubscriptionResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeSubscriptionResponse.ProtoReflect.Descriptor instead. +func (*InitializeSubscriptionResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{21} +} + +func (x *InitializeSubscriptionResponse) GetSubscriptionId() int64 { + if x != nil { + return x.SubscriptionId + } + return 0 +} + +func (x *InitializeSubscriptionResponse) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *InitializeSubscriptionResponse) GetHeads() []*TopicPosition { + if x != nil { + return x.Heads + } + return nil +} + +// Ensure a subscriber has a subscription in the topic. +type EnsureSubscriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The identifier for the subscribers current subscription. + SubscriptionId int64 `protobuf:"varint,1,opt,name=subscriptionId,proto3" json:"subscriptionId,omitempty"` + // A flag to indicate that the reconnect logic should force a reconnect + // request even if the subscriber is in the config map + ForceReconnect bool `protobuf:"varint,2,opt,name=forceReconnect,proto3" json:"forceReconnect,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSubscriptionRequest) Reset() { + *x = EnsureSubscriptionRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSubscriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSubscriptionRequest) ProtoMessage() {} + +func (x *EnsureSubscriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSubscriptionRequest.ProtoReflect.Descriptor instead. +func (*EnsureSubscriptionRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{22} +} + +func (x *EnsureSubscriptionRequest) GetSubscriptionId() int64 { + if x != nil { + return x.SubscriptionId + } + return 0 +} + +func (x *EnsureSubscriptionRequest) GetForceReconnect() bool { + if x != nil { + return x.ForceReconnect + } + return false +} + +// An element received by a subscriber after calling receive. +type TopicElement struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel that the element was received from. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The serialized binary value + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // The position the element was in the topic + Position *TopicPosition `protobuf:"bytes,3,opt,name=position,proto3" json:"position,omitempty"` + // the timestamp the value was published + Timestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TopicElement) Reset() { + *x = TopicElement{} + mi := &file_topic_service_messages_v1_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TopicElement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TopicElement) ProtoMessage() {} + +func (x *TopicElement) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TopicElement.ProtoReflect.Descriptor instead. +func (*TopicElement) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{23} +} + +func (x *TopicElement) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *TopicElement) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *TopicElement) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +func (x *TopicElement) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +type ReceiveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel to received from. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The maximum number of messages to return. + // If not set (or <= 0) multiple messages may be returned, in which case + // the exact number will be determined by the topic implementation + // on the server. + MaxMessages *int32 `protobuf:"varint,2,opt,name=maxMessages,proto3,oneof" json:"maxMessages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReceiveRequest) Reset() { + *x = ReceiveRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReceiveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiveRequest) ProtoMessage() {} + +func (x *ReceiveRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReceiveRequest.ProtoReflect.Descriptor instead. +func (*ReceiveRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{24} +} + +func (x *ReceiveRequest) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *ReceiveRequest) GetMaxMessages() int32 { + if x != nil && x.MaxMessages != nil { + return *x.MaxMessages + } + return 0 +} + +type ReceiveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The status of the receive result. + Status ReceiveStatus `protobuf:"varint,1,opt,name=status,proto3,enum=coherence.topic.v1.ReceiveStatus" json:"status,omitempty"` + // The serialized values received from the channel. + Values [][]byte `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` + // The new head position for the channel. + HeadPosition *TopicPosition `protobuf:"bytes,3,opt,name=headPosition,proto3" json:"headPosition,omitempty"` + // The count of the remaining values. + RemainingValues int32 `protobuf:"varint,4,opt,name=remainingValues,proto3" json:"remainingValues,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReceiveResponse) Reset() { + *x = ReceiveResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReceiveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiveResponse) ProtoMessage() {} + +func (x *ReceiveResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReceiveResponse.ProtoReflect.Descriptor instead. +func (*ReceiveResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{25} +} + +func (x *ReceiveResponse) GetStatus() ReceiveStatus { + if x != nil { + return x.Status + } + return ReceiveStatus_ReceiveSuccess +} + +func (x *ReceiveResponse) GetValues() [][]byte { + if x != nil { + return x.Values + } + return nil +} + +func (x *ReceiveResponse) GetHeadPosition() *TopicPosition { + if x != nil { + return x.HeadPosition + } + return nil +} + +func (x *ReceiveResponse) GetRemainingValues() int32 { + if x != nil { + return x.RemainingValues + } + return 0 +} + +// A request to seek (reposition) one or more channels. +type SeekRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The positions to seek to. + // + // Types that are valid to be assigned to Positions: + // + // *SeekRequest_ByPosition + // *SeekRequest_ByTimestamp + Positions isSeekRequest_Positions `protobuf_oneof:"positions"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekRequest) Reset() { + *x = SeekRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekRequest) ProtoMessage() {} + +func (x *SeekRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekRequest.ProtoReflect.Descriptor instead. +func (*SeekRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{26} +} + +func (x *SeekRequest) GetPositions() isSeekRequest_Positions { + if x != nil { + return x.Positions + } + return nil +} + +func (x *SeekRequest) GetByPosition() *MapOfChannelAndPosition { + if x != nil { + if x, ok := x.Positions.(*SeekRequest_ByPosition); ok { + return x.ByPosition + } + } + return nil +} + +func (x *SeekRequest) GetByTimestamp() *MapOfChannelAndTimestamp { + if x != nil { + if x, ok := x.Positions.(*SeekRequest_ByTimestamp); ok { + return x.ByTimestamp + } + } + return nil +} + +type isSeekRequest_Positions interface { + isSeekRequest_Positions() +} + +type SeekRequest_ByPosition struct { + // Seek to the specified positions in channels. + ByPosition *MapOfChannelAndPosition `protobuf:"bytes,1,opt,name=byPosition,proto3,oneof"` +} + +type SeekRequest_ByTimestamp struct { + // Seek to the specified timestamps in channels. + ByTimestamp *MapOfChannelAndTimestamp `protobuf:"bytes,2,opt,name=byTimestamp,proto3,oneof"` +} + +func (*SeekRequest_ByPosition) isSeekRequest_Positions() {} + +func (*SeekRequest_ByTimestamp) isSeekRequest_Positions() {} + +// The result of a seek request for a channel. +type SeekedPositions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The new head position. + Head *TopicPosition `protobuf:"bytes,2,opt,name=head,proto3" json:"head,omitempty"` + // The seeked to position. + SeekedTo *TopicPosition `protobuf:"bytes,3,opt,name=seekedTo,proto3" json:"seekedTo,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekedPositions) Reset() { + *x = SeekedPositions{} + mi := &file_topic_service_messages_v1_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekedPositions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekedPositions) ProtoMessage() {} + +func (x *SeekedPositions) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekedPositions.ProtoReflect.Descriptor instead. +func (*SeekedPositions) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{27} +} + +func (x *SeekedPositions) GetHead() *TopicPosition { + if x != nil { + return x.Head + } + return nil +} + +func (x *SeekedPositions) GetSeekedTo() *TopicPosition { + if x != nil { + return x.SeekedTo + } + return nil +} + +// The result of a seek request. +type SeekResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of SeekedPositions by channel. + Positions map[int32]*SeekedPositions `protobuf:"bytes,1,rep,name=positions,proto3" json:"positions,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SeekResponse) Reset() { + *x = SeekResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SeekResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SeekResponse) ProtoMessage() {} + +func (x *SeekResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SeekResponse.ProtoReflect.Descriptor instead. +func (*SeekResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{28} +} + +func (x *SeekResponse) GetPositions() map[int32]*SeekedPositions { + if x != nil { + return x.Positions + } + return nil +} + +// The result of a commit request +type CommitResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The channel committed. + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + // The position committed. + Position *TopicPosition `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + // The channel's head position. + Head *TopicPosition `protobuf:"bytes,3,opt,name=head,proto3" json:"head,omitempty"` + // The status of the commit response. + Status CommitResponseStatus `protobuf:"varint,4,opt,name=status,proto3,enum=coherence.topic.v1.CommitResponseStatus" json:"status,omitempty"` + // Any error that may hav occurred + Error *v1.ErrorMessage `protobuf:"bytes,5,opt,name=error,proto3,oneof" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommitResponse) Reset() { + *x = CommitResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommitResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommitResponse) ProtoMessage() {} + +func (x *CommitResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. +func (*CommitResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{29} +} + +func (x *CommitResponse) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *CommitResponse) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +func (x *CommitResponse) GetHead() *TopicPosition { + if x != nil { + return x.Head + } + return nil +} + +func (x *CommitResponse) GetStatus() CommitResponseStatus { + if x != nil { + return x.Status + } + return CommitResponseStatus_Committed +} + +func (x *CommitResponse) GetError() *v1.ErrorMessage { + if x != nil { + return x.Error + } + return nil +} + +// A channel and position. +type ChannelAndPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Channel int32 `protobuf:"varint,1,opt,name=channel,proto3" json:"channel,omitempty"` + Position *TopicPosition `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChannelAndPosition) Reset() { + *x = ChannelAndPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChannelAndPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChannelAndPosition) ProtoMessage() {} + +func (x *ChannelAndPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChannelAndPosition.ProtoReflect.Descriptor instead. +func (*ChannelAndPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{30} +} + +func (x *ChannelAndPosition) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *ChannelAndPosition) GetPosition() *TopicPosition { + if x != nil { + return x.Position + } + return nil +} + +// A map of topic channel identifier to position. +type MapOfChannelAndPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of channels to positions. + Positions map[int32]*TopicPosition `protobuf:"bytes,1,rep,name=positions,proto3" json:"positions,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapOfChannelAndPosition) Reset() { + *x = MapOfChannelAndPosition{} + mi := &file_topic_service_messages_v1_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapOfChannelAndPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapOfChannelAndPosition) ProtoMessage() {} + +func (x *MapOfChannelAndPosition) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapOfChannelAndPosition.ProtoReflect.Descriptor instead. +func (*MapOfChannelAndPosition) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{31} +} + +func (x *MapOfChannelAndPosition) GetPositions() map[int32]*TopicPosition { + if x != nil { + return x.Positions + } + return nil +} + +// A map of topic channel identifier to timestamps. +type MapOfChannelAndTimestamp struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The map of channels to timestamps. + Timestamps map[int32]*timestamppb.Timestamp `protobuf:"bytes,1,rep,name=timestamps,proto3" json:"timestamps,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MapOfChannelAndTimestamp) Reset() { + *x = MapOfChannelAndTimestamp{} + mi := &file_topic_service_messages_v1_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MapOfChannelAndTimestamp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MapOfChannelAndTimestamp) ProtoMessage() {} + +func (x *MapOfChannelAndTimestamp) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MapOfChannelAndTimestamp.ProtoReflect.Descriptor instead. +func (*MapOfChannelAndTimestamp) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{32} +} + +func (x *MapOfChannelAndTimestamp) GetTimestamps() map[int32]*timestamppb.Timestamp { + if x != nil { + return x.Timestamps + } + return nil +} + +var File_topic_service_messages_v1_proto protoreflect.FileDescriptor + +const file_topic_service_messages_v1_proto_rawDesc = "" + + "\n" + + "\x1ftopic_service_messages_v1.proto\x12\x12coherence.topic.v1\x1a\x18common_messages_v1.proto\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xc2\x01\n" + + "\x13TopicServiceRequest\x12?\n" + + "\x04type\x18\x01 \x01(\x0e2+.coherence.topic.v1.TopicServiceRequestTypeR\x04type\x12\x1d\n" + + "\aproxyId\x18\x02 \x01(\x05H\x00R\aproxyId\x88\x01\x01\x123\n" + + "\amessage\x18\x03 \x01(\v2\x14.google.protobuf.AnyH\x01R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_proxyIdB\n" + + "\n" + + "\b_message\"\xa7\x01\n" + + "\x14TopicServiceResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x124\n" + + "\x04type\x18\x02 \x01(\x0e2 .coherence.topic.v1.ResponseTypeR\x04type\x123\n" + + "\amessage\x18\x03 \x01(\v2\x14.google.protobuf.AnyH\x00R\amessage\x88\x01\x01B\n" + + "\n" + + "\b_message\"*\n" + + "\x12EnsureTopicRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\"I\n" + + "\x0fNamedTopicEvent\x126\n" + + "\x04type\x18\x01 \x01(\x0e2\".coherence.topic.v1.TopicEventTypeR\x04type\"\xa0\x01\n" + + "\x19EnsureChannelCountRequest\x12\x19\n" + + "\x05topic\x18\x01 \x01(\tH\x00R\x05topic\x88\x01\x01\x12$\n" + + "\rrequiredCount\x18\x02 \x01(\x05R\rrequiredCount\x12'\n" + + "\fchannelCount\x18\x03 \x01(\x05H\x01R\fchannelCount\x88\x01\x01B\b\n" + + "\x06_topicB\x0f\n" + + "\r_channelCount\"\xa1\x01\n" + + "\x1cEnsureSubscriberGroupRequest\x12(\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tR\x0fsubscriberGroup\x12\x1b\n" + + "\x06filter\x18\x03 \x01(\fH\x00R\x06filter\x88\x01\x01\x12!\n" + + "\textractor\x18\x04 \x01(\fH\x01R\textractor\x88\x01\x01B\t\n" + + "\a_filterB\f\n" + + "\n" + + "_extractor\"\x88\x01\n" + + "\x1bGetRemainingMessagesRequest\x12\x19\n" + + "\x05topic\x18\x01 \x01(\tH\x00R\x05topic\x88\x01\x01\x12(\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tR\x0fsubscriberGroup\x12\x1a\n" + + "\bchannels\x18\x03 \x03(\x05R\bchannelsB\b\n" + + "\x06_topic\"R\n" + + "\x16EnsurePublisherRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\x12\"\n" + + "\fchannelCount\x18\x02 \x01(\x05R\fchannelCount\"\x9d\x01\n" + + "\x17EnsurePublisherResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x12 \n" + + "\vpublisherId\x18\x02 \x01(\x03R\vpublisherId\x12\"\n" + + "\fchannelCount\x18\x03 \x01(\x05R\fchannelCount\x12\"\n" + + "\fmaxBatchSize\x18\x04 \x01(\x03R\fmaxBatchSize\"l\n" + + "\x0ePublisherEvent\x12:\n" + + "\x04type\x18\x01 \x01(\x0e2&.coherence.topic.v1.PublisherEventTypeR\x04type\x12\x1e\n" + + "\bchannels\x18\x02 \x03(\x05B\x02\x10\x01R\bchannels\"\x9a\x01\n" + + "\x0ePublishRequest\x12\x18\n" + + "\achannel\x18\x02 \x01(\x05R\achannel\x12\x16\n" + + "\x06values\x18\x03 \x03(\fR\x06values\x12;\n" + + "\x16notificationIdentifier\x18\x04 \x01(\x05H\x00R\x16notificationIdentifier\x88\x01\x01B\x19\n" + + "\x17_notificationIdentifier\"\x9b\x01\n" + + "\x14PublishedValueStatus\x12?\n" + + "\bposition\x18\x01 \x01(\v2!.coherence.topic.v1.TopicPositionH\x00R\bposition\x129\n" + + "\x05error\x18\x02 \x01(\v2!.coherence.common.v1.ErrorMessageH\x00R\x05errorB\a\n" + + "\x05state\"\x84\x02\n" + + "\rPublishResult\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x129\n" + + "\x06status\x18\x03 \x01(\x0e2!.coherence.topic.v1.PublishStatusR\x06status\x12$\n" + + "\racceptedCount\x18\x05 \x01(\x05R\racceptedCount\x12,\n" + + "\x11remainingCapacity\x18\x06 \x01(\x05R\x11remainingCapacity\x12J\n" + + "\vvalueStatus\x18\a \x03(\v2(.coherence.topic.v1.PublishedValueStatusR\vvalueStatus\";\n" + + "\rPagedPosition\x12\x12\n" + + "\x04page\x18\x01 \x01(\x03R\x04page\x12\x16\n" + + "\x06offset\x18\x02 \x01(\x05R\x06offset\"A\n" + + "\rTopicPosition\x120\n" + + "\bposition\x18\x01 \x01(\v2\x14.google.protobuf.AnyR\bposition\"\xbb\x01\n" + + "\x18EnsureSubscriberResponse\x12\x18\n" + + "\aproxyId\x18\x01 \x01(\x05R\aproxyId\x12D\n" + + "\fsubscriberId\x18\x02 \x01(\v2 .coherence.topic.v1.SubscriberIdR\fsubscriberId\x12?\n" + + "\agroupId\x18\x03 \x01(\v2%.coherence.topic.v1.SubscriberGroupIdR\agroupId\"\x91\x02\n" + + "\x17EnsureSubscriberRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\x12-\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tH\x00R\x0fsubscriberGroup\x88\x01\x01\x12\x1b\n" + + "\x06filter\x18\x03 \x01(\fH\x01R\x06filter\x88\x01\x01\x12!\n" + + "\textractor\x18\x04 \x01(\fH\x02R\textractor\x88\x01\x01\x12(\n" + + "\x0fcompleteOnEmpty\x18\x05 \x01(\bR\x0fcompleteOnEmpty\x12\x1a\n" + + "\bchannels\x18\x06 \x03(\x05R\bchannelsB\x12\n" + + "\x10_subscriberGroupB\t\n" + + "\a_filterB\f\n" + + "\n" + + "_extractor\"n\n" + + "\x0fSubscriberEvent\x12;\n" + + "\x04type\x18\x01 \x01(\x0e2'.coherence.topic.v1.SubscriberEventTypeR\x04type\x12\x1e\n" + + "\bchannels\x18\x02 \x03(\x05B\x02\x10\x01R\bchannels\"2\n" + + "\fSubscriberId\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + + "\x04uuid\x18\x04 \x01(\fR\x04uuid\"7\n" + + "\x11SubscriberGroupId\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x0e\n" + + "\x02id\x18\x02 \x01(\x03R\x02id\"\x89\x01\n" + + "\x1dInitializeSubscriptionRequest\x12\"\n" + + "\fdisconnected\x18\x01 \x01(\bR\fdisconnected\x12\x1c\n" + + "\treconnect\x18\x02 \x01(\bR\treconnect\x12&\n" + + "\x0eforceReconnect\x18\x03 \x01(\bR\x0eforceReconnect\"\xbb\x01\n" + + "\x1eInitializeSubscriptionResponse\x12&\n" + + "\x0esubscriptionId\x18\x01 \x01(\x03R\x0esubscriptionId\x128\n" + + "\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x127\n" + + "\x05heads\x18\x03 \x03(\v2!.coherence.topic.v1.TopicPositionR\x05heads\"k\n" + + "\x19EnsureSubscriptionRequest\x12&\n" + + "\x0esubscriptionId\x18\x01 \x01(\x03R\x0esubscriptionId\x12&\n" + + "\x0eforceReconnect\x18\x02 \x01(\bR\x0eforceReconnect\"\xb7\x01\n" + + "\fTopicElement\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\x12=\n" + + "\bposition\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\x128\n" + + "\ttimestamp\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\"a\n" + + "\x0eReceiveRequest\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12%\n" + + "\vmaxMessages\x18\x02 \x01(\x05H\x00R\vmaxMessages\x88\x01\x01B\x0e\n" + + "\f_maxMessages\"\xd5\x01\n" + + "\x0fReceiveResponse\x129\n" + + "\x06status\x18\x01 \x01(\x0e2!.coherence.topic.v1.ReceiveStatusR\x06status\x12\x16\n" + + "\x06values\x18\x02 \x03(\fR\x06values\x12E\n" + + "\fheadPosition\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\fheadPosition\x12(\n" + + "\x0fremainingValues\x18\x04 \x01(\x05R\x0fremainingValues\"\xbb\x01\n" + + "\vSeekRequest\x12M\n" + + "\n" + + "byPosition\x18\x01 \x01(\v2+.coherence.topic.v1.MapOfChannelAndPositionH\x00R\n" + + "byPosition\x12P\n" + + "\vbyTimestamp\x18\x02 \x01(\v2,.coherence.topic.v1.MapOfChannelAndTimestampH\x00R\vbyTimestampB\v\n" + + "\tpositions\"\x87\x01\n" + + "\x0fSeekedPositions\x125\n" + + "\x04head\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\x04head\x12=\n" + + "\bseekedTo\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\bseekedTo\"\xc0\x01\n" + + "\fSeekResponse\x12M\n" + + "\tpositions\x18\x01 \x03(\v2/.coherence.topic.v1.SeekResponse.PositionsEntryR\tpositions\x1aa\n" + + "\x0ePositionsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x129\n" + + "\x05value\x18\x02 \x01(\v2#.coherence.topic.v1.SeekedPositionsR\x05value:\x028\x01\"\xaa\x02\n" + + "\x0eCommitResponse\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12=\n" + + "\bposition\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\x125\n" + + "\x04head\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\x04head\x12@\n" + + "\x06status\x18\x04 \x01(\x0e2(.coherence.topic.v1.CommitResponseStatusR\x06status\x12<\n" + + "\x05error\x18\x05 \x01(\v2!.coherence.common.v1.ErrorMessageH\x00R\x05error\x88\x01\x01B\b\n" + + "\x06_error\"m\n" + + "\x12ChannelAndPosition\x12\x18\n" + + "\achannel\x18\x01 \x01(\x05R\achannel\x12=\n" + + "\bposition\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\bposition\"\xd4\x01\n" + + "\x17MapOfChannelAndPosition\x12X\n" + + "\tpositions\x18\x01 \x03(\v2:.coherence.topic.v1.MapOfChannelAndPosition.PositionsEntryR\tpositions\x1a_\n" + + "\x0ePositionsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x127\n" + + "\x05value\x18\x02 \x01(\v2!.coherence.topic.v1.TopicPositionR\x05value:\x028\x01\"\xd3\x01\n" + + "\x18MapOfChannelAndTimestamp\x12\\\n" + + "\n" + + "timestamps\x18\x01 \x03(\v2<.coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntryR\n" + + "timestamps\x1aY\n" + + "\x0fTimestampsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x05R\x03key\x120\n" + + "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01*\xcc\x04\n" + + "\x17TopicServiceRequestType\x12\x12\n" + + "\x0eRequestUnknown\x10\x00\x12\x0f\n" + + "\vEnsureTopic\x10\x01\x12\x10\n" + + "\fDestroyTopic\x10\x02\x12\x13\n" + + "\x0fGetChannelCount\x10\x03\x12\x17\n" + + "\x13GetSubscriberGroups\x10\x04\x12\x16\n" + + "\x12EnsureChannelCount\x10\x05\x12\x19\n" + + "\x15EnsureSubscriberGroup\x10\x06\x12\x1a\n" + + "\x16DestroySubscriberGroup\x10\a\x12\x18\n" + + "\x14GetRemainingMessages\x10\b\x12\f\n" + + "\bGetTails\x10\t\x12\x13\n" + + "\x0fEnsurePublisher\x10\n" + + "\x12\x14\n" + + "\x10DestroyPublisher\x10\v\x12\v\n" + + "\aPublish\x10\f\x12\x14\n" + + "\x10EnsureSubscriber\x10\r\x12\x15\n" + + "\x11DestroySubscriber\x10\x0e\x12\x1a\n" + + "\x16InitializeSubscription\x10\x0f\x12\x16\n" + + "\x12EnsureSubscription\x10\x10\x12\x16\n" + + "\x12GetSubscriberHeads\x10\x11\x12\x13\n" + + "\x0fGetLastCommited\x10\x12\x12\x14\n" + + "\x10GetOwnedChannels\x10\x13\x12\x17\n" + + "\x13SubscriberHeartbeat\x10\x14\x12\x17\n" + + "\x13IsPositionCommitted\x10\x15\x12\x12\n" + + "\x0ePeekAtPosition\x10\x16\x12\v\n" + + "\aReceive\x10\x17\x12\x12\n" + + "\x0eSeekSubscriber\x10\x18\x12\x12\n" + + "\x0eCommitPosition\x10\x19*&\n" + + "\fResponseType\x12\v\n" + + "\aMessage\x10\x00\x12\t\n" + + "\x05Event\x10\x01*6\n" + + "\x0eTopicEventType\x12\x10\n" + + "\fEventUnknown\x10\x00\x12\x12\n" + + "\x0eTopicDestroyed\x10\x01*\xad\x01\n" + + "\x12PublisherEventType\x12\x19\n" + + "\x15PublisherEventUnknown\x10\x00\x12\x16\n" + + "\x12PublisherConnected\x10\x01\x12\x19\n" + + "\x15PublisherDisconnected\x10\x02\x12\x1a\n" + + "\x16PublisherChannelsFreed\x10\x03\x12\x16\n" + + "\x12PublisherDestroyed\x10\x04\x12\x15\n" + + "\x11PublisherReleased\x10\x05*+\n" + + "\rPublishStatus\x12\v\n" + + "\aSuccess\x10\x00\x12\r\n" + + "\tTopicFull\x10\x01*\xb0\x02\n" + + "\x13SubscriberEventType\x12\x1a\n" + + "\x16SubscriberEventUnknown\x10\x00\x12\x1c\n" + + "\x18SubscriberGroupDestroyed\x10\x01\x12\x1f\n" + + "\x1bSubscriberChannelAllocation\x10\x02\x12\x1a\n" + + "\x16SubscriberChannelsLost\x10\x03\x12\x1e\n" + + "\x1aSubscriberChannelPopulated\x10\x04\x12\x19\n" + + "\x15SubscriberChannelHead\x10\x05\x12\x1a\n" + + "\x16SubscriberUnsubscribed\x10\x06\x12\x17\n" + + "\x13SubscriberDestroyed\x10\a\x12\x16\n" + + "\x12SubscriberReleased\x10\b\x12\x1a\n" + + "\x16SubscriberDisconnected\x10\t*p\n" + + "\rReceiveStatus\x12\x12\n" + + "\x0eReceiveSuccess\x10\x00\x12\x14\n" + + "\x10ChannelExhausted\x10\x01\x12\x1e\n" + + "\x1aChannelNotAllocatedChannel\x10\x02\x12\x15\n" + + "\x11UnknownSubscriber\x10\x03*k\n" + + "\x14CommitResponseStatus\x12\r\n" + + "\tCommitted\x10\x00\x12\x14\n" + + "\x10AlreadyCommitted\x10\x01\x12\f\n" + + "\bRejected\x10\x02\x12\v\n" + + "\aUnowned\x10\x03\x12\x13\n" + + "\x0fNothingToCommit\x10\x04Bf\n" + + "+com.oracle.coherence.grpc.messages.topic.v1P\x01Z5github.com/oracle/coherence-go-client/proto/v1/topicsb\x06proto3" + +var ( + file_topic_service_messages_v1_proto_rawDescOnce sync.Once + file_topic_service_messages_v1_proto_rawDescData []byte +) + +func file_topic_service_messages_v1_proto_rawDescGZIP() []byte { + file_topic_service_messages_v1_proto_rawDescOnce.Do(func() { + file_topic_service_messages_v1_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_topic_service_messages_v1_proto_rawDesc), len(file_topic_service_messages_v1_proto_rawDesc))) + }) + return file_topic_service_messages_v1_proto_rawDescData +} + +var file_topic_service_messages_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_topic_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_topic_service_messages_v1_proto_goTypes = []any{ + (TopicServiceRequestType)(0), // 0: coherence.topic.v1.TopicServiceRequestType + (ResponseType)(0), // 1: coherence.topic.v1.ResponseType + (TopicEventType)(0), // 2: coherence.topic.v1.TopicEventType + (PublisherEventType)(0), // 3: coherence.topic.v1.PublisherEventType + (PublishStatus)(0), // 4: coherence.topic.v1.PublishStatus + (SubscriberEventType)(0), // 5: coherence.topic.v1.SubscriberEventType + (ReceiveStatus)(0), // 6: coherence.topic.v1.ReceiveStatus + (CommitResponseStatus)(0), // 7: coherence.topic.v1.CommitResponseStatus + (*TopicServiceRequest)(nil), // 8: coherence.topic.v1.TopicServiceRequest + (*TopicServiceResponse)(nil), // 9: coherence.topic.v1.TopicServiceResponse + (*EnsureTopicRequest)(nil), // 10: coherence.topic.v1.EnsureTopicRequest + (*NamedTopicEvent)(nil), // 11: coherence.topic.v1.NamedTopicEvent + (*EnsureChannelCountRequest)(nil), // 12: coherence.topic.v1.EnsureChannelCountRequest + (*EnsureSubscriberGroupRequest)(nil), // 13: coherence.topic.v1.EnsureSubscriberGroupRequest + (*GetRemainingMessagesRequest)(nil), // 14: coherence.topic.v1.GetRemainingMessagesRequest + (*EnsurePublisherRequest)(nil), // 15: coherence.topic.v1.EnsurePublisherRequest + (*EnsurePublisherResponse)(nil), // 16: coherence.topic.v1.EnsurePublisherResponse + (*PublisherEvent)(nil), // 17: coherence.topic.v1.PublisherEvent + (*PublishRequest)(nil), // 18: coherence.topic.v1.PublishRequest + (*PublishedValueStatus)(nil), // 19: coherence.topic.v1.PublishedValueStatus + (*PublishResult)(nil), // 20: coherence.topic.v1.PublishResult + (*PagedPosition)(nil), // 21: coherence.topic.v1.PagedPosition + (*TopicPosition)(nil), // 22: coherence.topic.v1.TopicPosition + (*EnsureSubscriberResponse)(nil), // 23: coherence.topic.v1.EnsureSubscriberResponse + (*EnsureSubscriberRequest)(nil), // 24: coherence.topic.v1.EnsureSubscriberRequest + (*SubscriberEvent)(nil), // 25: coherence.topic.v1.SubscriberEvent + (*SubscriberId)(nil), // 26: coherence.topic.v1.SubscriberId + (*SubscriberGroupId)(nil), // 27: coherence.topic.v1.SubscriberGroupId + (*InitializeSubscriptionRequest)(nil), // 28: coherence.topic.v1.InitializeSubscriptionRequest + (*InitializeSubscriptionResponse)(nil), // 29: coherence.topic.v1.InitializeSubscriptionResponse + (*EnsureSubscriptionRequest)(nil), // 30: coherence.topic.v1.EnsureSubscriptionRequest + (*TopicElement)(nil), // 31: coherence.topic.v1.TopicElement + (*ReceiveRequest)(nil), // 32: coherence.topic.v1.ReceiveRequest + (*ReceiveResponse)(nil), // 33: coherence.topic.v1.ReceiveResponse + (*SeekRequest)(nil), // 34: coherence.topic.v1.SeekRequest + (*SeekedPositions)(nil), // 35: coherence.topic.v1.SeekedPositions + (*SeekResponse)(nil), // 36: coherence.topic.v1.SeekResponse + (*CommitResponse)(nil), // 37: coherence.topic.v1.CommitResponse + (*ChannelAndPosition)(nil), // 38: coherence.topic.v1.ChannelAndPosition + (*MapOfChannelAndPosition)(nil), // 39: coherence.topic.v1.MapOfChannelAndPosition + (*MapOfChannelAndTimestamp)(nil), // 40: coherence.topic.v1.MapOfChannelAndTimestamp + nil, // 41: coherence.topic.v1.SeekResponse.PositionsEntry + nil, // 42: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + nil, // 43: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + (*anypb.Any)(nil), // 44: google.protobuf.Any + (*v1.ErrorMessage)(nil), // 45: coherence.common.v1.ErrorMessage + (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp +} +var file_topic_service_messages_v1_proto_depIdxs = []int32{ + 0, // 0: coherence.topic.v1.TopicServiceRequest.type:type_name -> coherence.topic.v1.TopicServiceRequestType + 44, // 1: coherence.topic.v1.TopicServiceRequest.message:type_name -> google.protobuf.Any + 1, // 2: coherence.topic.v1.TopicServiceResponse.type:type_name -> coherence.topic.v1.ResponseType + 44, // 3: coherence.topic.v1.TopicServiceResponse.message:type_name -> google.protobuf.Any + 2, // 4: coherence.topic.v1.NamedTopicEvent.type:type_name -> coherence.topic.v1.TopicEventType + 3, // 5: coherence.topic.v1.PublisherEvent.type:type_name -> coherence.topic.v1.PublisherEventType + 22, // 6: coherence.topic.v1.PublishedValueStatus.position:type_name -> coherence.topic.v1.TopicPosition + 45, // 7: coherence.topic.v1.PublishedValueStatus.error:type_name -> coherence.common.v1.ErrorMessage + 4, // 8: coherence.topic.v1.PublishResult.status:type_name -> coherence.topic.v1.PublishStatus + 19, // 9: coherence.topic.v1.PublishResult.valueStatus:type_name -> coherence.topic.v1.PublishedValueStatus + 44, // 10: coherence.topic.v1.TopicPosition.position:type_name -> google.protobuf.Any + 26, // 11: coherence.topic.v1.EnsureSubscriberResponse.subscriberId:type_name -> coherence.topic.v1.SubscriberId + 27, // 12: coherence.topic.v1.EnsureSubscriberResponse.groupId:type_name -> coherence.topic.v1.SubscriberGroupId + 5, // 13: coherence.topic.v1.SubscriberEvent.type:type_name -> coherence.topic.v1.SubscriberEventType + 46, // 14: coherence.topic.v1.InitializeSubscriptionResponse.timestamp:type_name -> google.protobuf.Timestamp + 22, // 15: coherence.topic.v1.InitializeSubscriptionResponse.heads:type_name -> coherence.topic.v1.TopicPosition + 22, // 16: coherence.topic.v1.TopicElement.position:type_name -> coherence.topic.v1.TopicPosition + 46, // 17: coherence.topic.v1.TopicElement.timestamp:type_name -> google.protobuf.Timestamp + 6, // 18: coherence.topic.v1.ReceiveResponse.status:type_name -> coherence.topic.v1.ReceiveStatus + 22, // 19: coherence.topic.v1.ReceiveResponse.headPosition:type_name -> coherence.topic.v1.TopicPosition + 39, // 20: coherence.topic.v1.SeekRequest.byPosition:type_name -> coherence.topic.v1.MapOfChannelAndPosition + 40, // 21: coherence.topic.v1.SeekRequest.byTimestamp:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp + 22, // 22: coherence.topic.v1.SeekedPositions.head:type_name -> coherence.topic.v1.TopicPosition + 22, // 23: coherence.topic.v1.SeekedPositions.seekedTo:type_name -> coherence.topic.v1.TopicPosition + 41, // 24: coherence.topic.v1.SeekResponse.positions:type_name -> coherence.topic.v1.SeekResponse.PositionsEntry + 22, // 25: coherence.topic.v1.CommitResponse.position:type_name -> coherence.topic.v1.TopicPosition + 22, // 26: coherence.topic.v1.CommitResponse.head:type_name -> coherence.topic.v1.TopicPosition + 7, // 27: coherence.topic.v1.CommitResponse.status:type_name -> coherence.topic.v1.CommitResponseStatus + 45, // 28: coherence.topic.v1.CommitResponse.error:type_name -> coherence.common.v1.ErrorMessage + 22, // 29: coherence.topic.v1.ChannelAndPosition.position:type_name -> coherence.topic.v1.TopicPosition + 42, // 30: coherence.topic.v1.MapOfChannelAndPosition.positions:type_name -> coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + 43, // 31: coherence.topic.v1.MapOfChannelAndTimestamp.timestamps:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + 35, // 32: coherence.topic.v1.SeekResponse.PositionsEntry.value:type_name -> coherence.topic.v1.SeekedPositions + 22, // 33: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry.value:type_name -> coherence.topic.v1.TopicPosition + 46, // 34: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry.value:type_name -> google.protobuf.Timestamp + 35, // [35:35] is the sub-list for method output_type + 35, // [35:35] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name +} + +func init() { file_topic_service_messages_v1_proto_init() } +func file_topic_service_messages_v1_proto_init() { + if File_topic_service_messages_v1_proto != nil { + return + } + file_topic_service_messages_v1_proto_msgTypes[0].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[1].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[4].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[5].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[6].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[10].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[11].OneofWrappers = []any{ + (*PublishedValueStatus_Position)(nil), + (*PublishedValueStatus_Error)(nil), + } + file_topic_service_messages_v1_proto_msgTypes[16].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[24].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[26].OneofWrappers = []any{ + (*SeekRequest_ByPosition)(nil), + (*SeekRequest_ByTimestamp)(nil), + } + file_topic_service_messages_v1_proto_msgTypes[29].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_topic_service_messages_v1_proto_rawDesc), len(file_topic_service_messages_v1_proto_rawDesc)), + NumEnums: 8, + NumMessages: 36, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_topic_service_messages_v1_proto_goTypes, + DependencyIndexes: file_topic_service_messages_v1_proto_depIdxs, + EnumInfos: file_topic_service_messages_v1_proto_enumTypes, + MessageInfos: file_topic_service_messages_v1_proto_msgTypes, + }.Build() + File_topic_service_messages_v1_proto = out.File + file_topic_service_messages_v1_proto_goTypes = nil + file_topic_service_messages_v1_proto_depIdxs = nil +} diff --git a/proto/v1/proxy_service_messages_v1.pb.go b/proto/v1/proxy_service_messages_v1.pb.go index 00a21335..71696ccf 100644 --- a/proto/v1/proxy_service_messages_v1.pb.go +++ b/proto/v1/proxy_service_messages_v1.pb.go @@ -327,7 +327,9 @@ type InitRequest struct { // The requested frequency that heartbeat messages should be sent by the server (in millis) Heartbeat *int64 `protobuf:"varint,7,opt,name=heartbeat,proto3,oneof" json:"heartbeat,omitempty"` // The optional client UUID (usually from Coherence clients that have a local Member UUID). - ClientUuid []byte `protobuf:"bytes,8,opt,name=clientUuid,proto3,oneof" json:"clientUuid,omitempty"` + ClientUuid []byte `protobuf:"bytes,8,opt,name=clientUuid,proto3,oneof" json:"clientUuid,omitempty"` + // The client's member identity + Identity *ClientMemberIdentity `protobuf:"bytes,9,opt,name=identity,proto3,oneof" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -411,6 +413,13 @@ func (x *InitRequest) GetClientUuid() []byte { return nil } +func (x *InitRequest) GetIdentity() *ClientMemberIdentity { + if x != nil { + return x.Identity + } + return nil +} + // The response to an InitRequest type InitResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -502,6 +511,133 @@ func (x *InitResponse) GetProxyMemberUuid() []byte { return nil } +type ClientMemberIdentity struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the cluster with which this member is associated. + ClusterName *string `protobuf:"bytes,1,opt,name=clusterName,proto3,oneof" json:"clusterName,omitempty"` + // The Member's machine Id. This identifier should be the same for Members that are on + // the same physical machine, and ideally different for Members that are on different + // physical machines. + MachineId int32 `protobuf:"varint,2,opt,name=machineId,proto3" json:"machineId,omitempty"` + // The configured name for the Machine (such as a host name) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple servers, + // and may be used as the basis for determining the MachineId property. + MachineName *string `protobuf:"bytes,3,opt,name=machineName,proto3,oneof" json:"machineName,omitempty"` + // The configured name for the Member. This name is used for logging purposes and + // to differentiate among Members running within a particular process. + MemberName *string `protobuf:"bytes,4,opt,name=memberName,proto3,oneof" json:"memberName,omitempty"` + // The member priority + Priority int32 `protobuf:"varint,5,opt,name=priority,proto3" json:"priority,omitempty"` + // The configured name for the Process (such as a JVM) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple processes on a a single machine. + ProcessName *string `protobuf:"bytes,6,opt,name=processName,proto3,oneof" json:"processName,omitempty"` + // The configured name for the Rack (such as a physical rack, cage or blade frame) in which + // this Member resides. This name is used for logging purposes and to differentiate among multiple + // racks within a particular data center, for example. + RackName *string `protobuf:"bytes,7,opt,name=rackName,proto3,oneof" json:"rackName,omitempty"` + // The configured name for the Site (such as a data center) in which this Member resides. + // This name is used for logging purposes and to differentiate among multiple geographic sites. + SiteName *string `protobuf:"bytes,8,opt,name=siteName,proto3,oneof" json:"siteName,omitempty"` + // The configured role name for the Member. This role is completely definable by the application, + // and can be used to determine what Members to use for specific purposes. + RoleName *string `protobuf:"bytes,9,opt,name=roleName,proto3,oneof" json:"roleName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientMemberIdentity) Reset() { + *x = ClientMemberIdentity{} + mi := &file_proxy_service_messages_v1_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientMemberIdentity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientMemberIdentity) ProtoMessage() {} + +func (x *ClientMemberIdentity) ProtoReflect() protoreflect.Message { + mi := &file_proxy_service_messages_v1_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientMemberIdentity.ProtoReflect.Descriptor instead. +func (*ClientMemberIdentity) Descriptor() ([]byte, []int) { + return file_proxy_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +func (x *ClientMemberIdentity) GetClusterName() string { + if x != nil && x.ClusterName != nil { + return *x.ClusterName + } + return "" +} + +func (x *ClientMemberIdentity) GetMachineId() int32 { + if x != nil { + return x.MachineId + } + return 0 +} + +func (x *ClientMemberIdentity) GetMachineName() string { + if x != nil && x.MachineName != nil { + return *x.MachineName + } + return "" +} + +func (x *ClientMemberIdentity) GetMemberName() string { + if x != nil && x.MemberName != nil { + return *x.MemberName + } + return "" +} + +func (x *ClientMemberIdentity) GetPriority() int32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *ClientMemberIdentity) GetProcessName() string { + if x != nil && x.ProcessName != nil { + return *x.ProcessName + } + return "" +} + +func (x *ClientMemberIdentity) GetRackName() string { + if x != nil && x.RackName != nil { + return *x.RackName + } + return "" +} + +func (x *ClientMemberIdentity) GetSiteName() string { + if x != nil && x.SiteName != nil { + return *x.SiteName + } + return "" +} + +func (x *ClientMemberIdentity) GetRoleName() string { + if x != nil && x.RoleName != nil { + return *x.RoleName + } + return "" +} + var File_proxy_service_messages_v1_proto protoreflect.FileDescriptor const file_proxy_service_messages_v1_proto_rawDesc = "" + @@ -529,7 +665,7 @@ const file_proxy_service_messages_v1_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12*\n" + "\x05value\x18\x02 \x01(\v2\x14.google.protobuf.AnyR\x05value:\x028\x01B\n" + "\n" + - "\bresponse\"\xa2\x02\n" + + "\bresponse\"\xfa\x02\n" + "\vInitRequest\x12\x14\n" + "\x05scope\x18\x02 \x01(\tR\x05scope\x12\x16\n" + "\x06format\x18\x03 \x01(\tR\x06format\x12\x1a\n" + @@ -539,17 +675,38 @@ const file_proxy_service_messages_v1_proto_rawDesc = "" + "\theartbeat\x18\a \x01(\x03H\x00R\theartbeat\x88\x01\x01\x12#\n" + "\n" + "clientUuid\x18\b \x01(\fH\x01R\n" + - "clientUuid\x88\x01\x01B\f\n" + + "clientUuid\x88\x01\x01\x12I\n" + + "\bidentity\x18\t \x01(\v2(.coherence.proxy.v1.ClientMemberIdentityH\x02R\bidentity\x88\x01\x01B\f\n" + "\n" + "_heartbeatB\r\n" + - "\v_clientUuid\"\xde\x01\n" + + "\v_clientUuidB\v\n" + + "\t_identity\"\xde\x01\n" + "\fInitResponse\x12\x12\n" + "\x04uuid\x18\x01 \x01(\fR\x04uuid\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12&\n" + "\x0eencodedVersion\x18\x03 \x01(\x05R\x0eencodedVersion\x12(\n" + "\x0fprotocolVersion\x18\x04 \x01(\x05R\x0fprotocolVersion\x12$\n" + "\rproxyMemberId\x18\x05 \x01(\x05R\rproxyMemberId\x12(\n" + - "\x0fproxyMemberUuid\x18\x06 \x01(\fR\x0fproxyMemberUuidB_\n" + + "\x0fproxyMemberUuid\x18\x06 \x01(\fR\x0fproxyMemberUuid\"\xb3\x03\n" + + "\x14ClientMemberIdentity\x12%\n" + + "\vclusterName\x18\x01 \x01(\tH\x00R\vclusterName\x88\x01\x01\x12\x1c\n" + + "\tmachineId\x18\x02 \x01(\x05R\tmachineId\x12%\n" + + "\vmachineName\x18\x03 \x01(\tH\x01R\vmachineName\x88\x01\x01\x12#\n" + + "\n" + + "memberName\x18\x04 \x01(\tH\x02R\n" + + "memberName\x88\x01\x01\x12\x1a\n" + + "\bpriority\x18\x05 \x01(\x05R\bpriority\x12%\n" + + "\vprocessName\x18\x06 \x01(\tH\x03R\vprocessName\x88\x01\x01\x12\x1f\n" + + "\brackName\x18\a \x01(\tH\x04R\brackName\x88\x01\x01\x12\x1f\n" + + "\bsiteName\x18\b \x01(\tH\x05R\bsiteName\x88\x01\x01\x12\x1f\n" + + "\broleName\x18\t \x01(\tH\x06R\broleName\x88\x01\x01B\x0e\n" + + "\f_clusterNameB\x0e\n" + + "\f_machineNameB\r\n" + + "\v_memberNameB\x0e\n" + + "\f_processNameB\v\n" + + "\t_rackNameB\v\n" + + "\t_siteNameB\v\n" + + "\t_roleNameB_\n" + "+com.oracle.coherence.grpc.messages.proxy.v1P\x01Z.github.com/oracle/coherence-go-client/proto/v1b\x06proto3" var ( @@ -564,37 +721,39 @@ func file_proxy_service_messages_v1_proto_rawDescGZIP() []byte { return file_proxy_service_messages_v1_proto_rawDescData } -var file_proxy_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_proxy_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_proxy_service_messages_v1_proto_goTypes = []any{ - (*ProxyRequest)(nil), // 0: coherence.proxy.v1.ProxyRequest - (*ProxyResponse)(nil), // 1: coherence.proxy.v1.ProxyResponse - (*InitRequest)(nil), // 2: coherence.proxy.v1.InitRequest - (*InitResponse)(nil), // 3: coherence.proxy.v1.InitResponse - nil, // 4: coherence.proxy.v1.ProxyRequest.ContextEntry - nil, // 5: coherence.proxy.v1.ProxyResponse.ContextEntry - (*anypb.Any)(nil), // 6: google.protobuf.Any - (*HeartbeatMessage)(nil), // 7: coherence.common.v1.HeartbeatMessage - (*ErrorMessage)(nil), // 8: coherence.common.v1.ErrorMessage - (*Complete)(nil), // 9: coherence.common.v1.Complete + (*ProxyRequest)(nil), // 0: coherence.proxy.v1.ProxyRequest + (*ProxyResponse)(nil), // 1: coherence.proxy.v1.ProxyResponse + (*InitRequest)(nil), // 2: coherence.proxy.v1.InitRequest + (*InitResponse)(nil), // 3: coherence.proxy.v1.InitResponse + (*ClientMemberIdentity)(nil), // 4: coherence.proxy.v1.ClientMemberIdentity + nil, // 5: coherence.proxy.v1.ProxyRequest.ContextEntry + nil, // 6: coherence.proxy.v1.ProxyResponse.ContextEntry + (*anypb.Any)(nil), // 7: google.protobuf.Any + (*HeartbeatMessage)(nil), // 8: coherence.common.v1.HeartbeatMessage + (*ErrorMessage)(nil), // 9: coherence.common.v1.ErrorMessage + (*Complete)(nil), // 10: coherence.common.v1.Complete } var file_proxy_service_messages_v1_proto_depIdxs = []int32{ 2, // 0: coherence.proxy.v1.ProxyRequest.init:type_name -> coherence.proxy.v1.InitRequest - 6, // 1: coherence.proxy.v1.ProxyRequest.message:type_name -> google.protobuf.Any - 7, // 2: coherence.proxy.v1.ProxyRequest.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage - 4, // 3: coherence.proxy.v1.ProxyRequest.context:type_name -> coherence.proxy.v1.ProxyRequest.ContextEntry + 7, // 1: coherence.proxy.v1.ProxyRequest.message:type_name -> google.protobuf.Any + 8, // 2: coherence.proxy.v1.ProxyRequest.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage + 5, // 3: coherence.proxy.v1.ProxyRequest.context:type_name -> coherence.proxy.v1.ProxyRequest.ContextEntry 3, // 4: coherence.proxy.v1.ProxyResponse.init:type_name -> coherence.proxy.v1.InitResponse - 6, // 5: coherence.proxy.v1.ProxyResponse.message:type_name -> google.protobuf.Any - 8, // 6: coherence.proxy.v1.ProxyResponse.error:type_name -> coherence.common.v1.ErrorMessage - 9, // 7: coherence.proxy.v1.ProxyResponse.complete:type_name -> coherence.common.v1.Complete - 7, // 8: coherence.proxy.v1.ProxyResponse.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage - 5, // 9: coherence.proxy.v1.ProxyResponse.context:type_name -> coherence.proxy.v1.ProxyResponse.ContextEntry - 6, // 10: coherence.proxy.v1.ProxyRequest.ContextEntry.value:type_name -> google.protobuf.Any - 6, // 11: coherence.proxy.v1.ProxyResponse.ContextEntry.value:type_name -> google.protobuf.Any - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 7, // 5: coherence.proxy.v1.ProxyResponse.message:type_name -> google.protobuf.Any + 9, // 6: coherence.proxy.v1.ProxyResponse.error:type_name -> coherence.common.v1.ErrorMessage + 10, // 7: coherence.proxy.v1.ProxyResponse.complete:type_name -> coherence.common.v1.Complete + 8, // 8: coherence.proxy.v1.ProxyResponse.heartbeat:type_name -> coherence.common.v1.HeartbeatMessage + 6, // 9: coherence.proxy.v1.ProxyResponse.context:type_name -> coherence.proxy.v1.ProxyResponse.ContextEntry + 4, // 10: coherence.proxy.v1.InitRequest.identity:type_name -> coherence.proxy.v1.ClientMemberIdentity + 7, // 11: coherence.proxy.v1.ProxyRequest.ContextEntry.value:type_name -> google.protobuf.Any + 7, // 12: coherence.proxy.v1.ProxyResponse.ContextEntry.value:type_name -> google.protobuf.Any + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_proxy_service_messages_v1_proto_init() } @@ -616,13 +775,14 @@ func file_proxy_service_messages_v1_proto_init() { (*ProxyResponse_Heartbeat)(nil), } file_proxy_service_messages_v1_proto_msgTypes[2].OneofWrappers = []any{} + file_proxy_service_messages_v1_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_service_messages_v1_proto_rawDesc), len(file_proxy_service_messages_v1_proto_rawDesc)), NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/scripts/run-compat-ce.sh b/scripts/run-compat-ce.sh index 51cf131f..1485d63b 100755 --- a/scripts/run-compat-ce.sh +++ b/scripts/run-compat-ce.sh @@ -61,3 +61,6 @@ COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax echo "Coherence CE 25.03.1 All Tests gRPC v1" COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=25.03.1 make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone + +echo "Coherence CE 25.09-SNAPSHOT with topics" +COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax,queues COHERENCE_VERSION=25.09-SNAPSHOT make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics diff --git a/test/e2e/topics/doc.go b/test/e2e/topics/doc.go new file mode 100644 index 00000000..f5a1da8d --- /dev/null +++ b/test/e2e/topics/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +/* +Package topic provides integration tests for the coherence-go-client with topic for 25.03 and above. +*/ +package topics diff --git a/test/e2e/topics/suite_test.go b/test/e2e/topics/suite_test.go new file mode 100644 index 00000000..439d91b4 --- /dev/null +++ b/test/e2e/topics/suite_test.go @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topics + +import ( + "github.com/oracle/coherence-go-client/v2/test/utils" + "testing" +) + +// The entry point for the test suite +func TestMain(m *testing.M) { + utils.RunTest(m, 1408, 30000, 8080, true) +} diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go new file mode 100644 index 00000000..ee18e272 --- /dev/null +++ b/test/e2e/topics/topics_test.go @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topics + +import ( + "context" + "fmt" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/extractors" + "github.com/oracle/coherence-go-client/v2/coherence/filters" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/topic" + "github.com/oracle/coherence-go-client/v2/test/utils" + "log" + "testing" + "time" +) + +func TestBasicTopicCreatedAndDestroy(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + topic1 coherence.NamedTopic[string] + ctx = context.Background() + ) + + const topicName = "my-topic" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + // get a NamedQueue with name "my-queue" + topic1, err = coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + utils.Sleep(5) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // test again and we should get error + err = topic1.Destroy(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + +func TestBasicTopicAnonPubSub(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + topic1 coherence.NamedTopic[string] + ctx = context.Background() + ) + + const topicName = "my-topic-anon" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + // get a NamedQueue with name "my-queue" + topic1, err = coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + // create a subscriber first + sub1, err := topic1.CreateSubscriber(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = sub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = pub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + ctx = context.Background() + ) + + const topicName = "my-topic-anon" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + sub1, err := coherence.CreateSubscriber[string](ctx, session1, topicName) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + fmt.Println("Subscriber created", sub1) + + pub1, err := coherence.CreatePublisher[string](ctx, session1, topicName) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = pub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // try to close again + err = pub1.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) + + // get teh topic so we can destroy + topic1, err := coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func TestSubscriberWithFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + topic1 coherence.NamedTopic[utils.Person] + ctx = context.Background() + ) + + const topicName = "my-topic-anon-filter" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + // create a subscriber with a filter + sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + p := utils.Person{ + ID: i, + Name: fmt.Sprintf("my-value-%d", i), + Age: 10 + i, + } + status, err2 := pub1.Publish(ctx, p) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = sub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = sub1.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} From 72584ca1a3d16ccdf133019824d3670fc7ead2be Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 3 Jul 2025 12:38:12 +0800 Subject: [PATCH 02/44] Fix some CI/CD issues --- .github/workflows/build-topics.yaml | 2 +- coherence/topics.go | 17 ++++++++++++++++ coherence/v1queues.go | 20 +------------------ .../src/main/resources/test-cache-config.xml | 2 +- test/e2e/topics/doc.go | 4 ++-- test/e2e/topics/suite_test.go | 2 +- 6 files changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-topics.yaml b/.github/workflows/build-topics.yaml index 4fc0afa2..b2a7db53 100644 --- a/.github/workflows/build-topics.yaml +++ b/.github/workflows/build-topics.yaml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03.2-SNAPSHOT + - 25.09-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/coherence/topics.go b/coherence/topics.go index 8d0d80f3..33f808d2 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -892,3 +892,20 @@ func newNamedTopicRequest(topicID *int32, reqType pb1topics.TopicServiceRequestT } return anyReq, nil } + +// submitTopicRequest submits a request to the stream manager and returns named topic request. +func (m *streamManagerV1) submitTopicRequest(req *pb1.ProxyRequest, requestType pb1topics.TopicServiceRequestType) (proxyRequestChannel, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + // create a channel for the response + ch := make(chan responseMessage) + + r := proxyRequestChannel{ch: ch} + + // save the request in the map keyed by request id + m.requests[req.Id] = r + m.session.debugConnection("id: %v submit topic request: %v %v", req.Id, requestType, req) + + return r, m.eventStream.grpcStream.Send(req) +} diff --git a/coherence/v1queues.go b/coherence/v1queues.go index 5218a483..de77c112 100644 --- a/coherence/v1queues.go +++ b/coherence/v1queues.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ @@ -8,7 +8,6 @@ package coherence import ( "context" - pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -38,23 +37,6 @@ func (m *streamManagerV1) submitQueueRequest(req *pb1.ProxyRequest, requestType return r, m.eventStream.grpcStream.Send(req) } -// submitTopicRequest submits a request to the stream manager and returns named topic request. -func (m *streamManagerV1) submitTopicRequest(req *pb1.ProxyRequest, requestType pb1topics.TopicServiceRequestType) (proxyRequestChannel, error) { - m.mutex.Lock() - defer m.mutex.Unlock() - - // create a channel for the response - ch := make(chan responseMessage) - - r := proxyRequestChannel{ch: ch} - - // save the request in the map keyed by request id - m.requests[req.Id] = r - m.session.debugConnection("id: %v submit queue request: %v %v", req.Id, requestType, req) - - return r, m.eventStream.grpcStream.Send(req) -} - // genericQueueRequest issues a generic request that is further defined by the reqType. func (m *streamManagerV1) genericQueueRequest(ctx context.Context, reqType pb1.NamedQueueRequestType, queue string) error { req, err := m.newGenericNamedQueueRequest(queue, reqType) diff --git a/java/coherence-go-test/src/main/resources/test-cache-config.xml b/java/coherence-go-test/src/main/resources/test-cache-config.xml index 222767b8..2ea736b9 100644 --- a/java/coherence-go-test/src/main/resources/test-cache-config.xml +++ b/java/coherence-go-test/src/main/resources/test-cache-config.xml @@ -1,6 +1,6 @@ diff --git a/test/e2e/topics/doc.go b/test/e2e/topics/doc.go index f5a1da8d..bfe76761 100644 --- a/test/e2e/topics/doc.go +++ b/test/e2e/topics/doc.go @@ -1,10 +1,10 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ /* -Package topic provides integration tests for the coherence-go-client with topic for 25.03 and above. +Package topic provides integration tests for the coherence-go-client with topic for 25.09 and above. */ package topics diff --git a/test/e2e/topics/suite_test.go b/test/e2e/topics/suite_test.go index 439d91b4..c14a59dd 100644 --- a/test/e2e/topics/suite_test.go +++ b/test/e2e/topics/suite_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ From 184dfc167406511e1f6fcba71d23c20598a9bb1b Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 3 Jul 2025 12:44:47 +0800 Subject: [PATCH 03/44] Remove duplication --- coherence/topics.go | 54 --------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/coherence/topics.go b/coherence/topics.go index 33f808d2..9982455d 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -120,11 +120,6 @@ func GetNamedTopic[V any](ctx context.Context, session *Session, topicName strin session.topics[topicName] = newTopic - _, err = session.v1StreamManagerTopics.ensureChannelCount(ctx, topicName, topicOptions) - if err != nil { - return nil, err - } - return newTopic, nil } @@ -399,41 +394,6 @@ func CreateSubscriber[V any](ctx context.Context, session *Session, topicName st return newSubscriber[V](session, nil, result, topicName, subscriberOptions) } -// ensureChannelCount issues the ensure topic channels -func (m *streamManagerV1) ensureChannelCount(ctx context.Context, topicName string, opts *topic.Options) (*int32, error) { - req, err := m.newEnsureChannelRequest(topicName, opts.ChannelCount, opts.ChannelCount) - if err != nil { - return nil, err - } - - requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureChannelCount) - if err != nil { - return nil, err - } - - newCtx, cancel := m.session.ensureContext(ctx) - if cancel != nil { - defer cancel() - } - - defer m.cleanupRequest(req.Id) - - result, err1 := waitForResponse(newCtx, requestType.ch, true) - if err1 != nil { - return nil, err1 - } - - var message = &wrapperspb.Int32Value{} - if err = result.UnmarshalTo(message); err != nil { - err = getUnmarshallError("ensure response", err) - return nil, err - } - - actualChannelCount := &message.Value - - return actualChannelCount, nil -} - // ensurePublisher ensures a publisher. func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, channelCount int32) (*publisher.EnsurePublisherResult, error) { req, err := m.newEnsurePublisherRequest(topicName, channelCount) @@ -712,20 +672,6 @@ func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], d return nil } -func (m *streamManagerV1) newEnsureChannelRequest(topicName string, requiredCount, channelCount int32) (*pb1.ProxyRequest, error) { - req := &pb1topics.EnsureChannelCountRequest{ - Topic: &topicName, - RequiredCount: requiredCount, - ChannelCount: &channelCount, - } - - anyReq, err := anypb.New(req) - if err != nil { - return nil, err - } - return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureChannelCount, anyReq) -} - func (m *streamManagerV1) newEnsurePublisherRequest(topicName string, channelCount int32) (*pb1.ProxyRequest, error) { req := &pb1topics.EnsurePublisherRequest{ Topic: topicName, From 5949c92468953cb2435ea61b76cbcae42d3d3aa3 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 13:44:43 +0800 Subject: [PATCH 04/44] Add DAPR Tests --- .github/workflows/build-dapr.yaml | 84 ++++++++++++++++ Makefile | 9 ++ scripts/run-test-dapr.sh | 95 +++++++++++++++++++ .../my-dapr-app/components/coherence.yaml | 17 ++++ test/dapr/my-dapr-app/go.mod | 23 +++++ test/dapr/my-dapr-app/go.sum | 48 ++++++++++ test/dapr/my-dapr-app/main.go | 40 ++++++++ 7 files changed, 316 insertions(+) create mode 100644 .github/workflows/build-dapr.yaml create mode 100755 scripts/run-test-dapr.sh create mode 100644 test/dapr/my-dapr-app/components/coherence.yaml create mode 100644 test/dapr/my-dapr-app/go.mod create mode 100644 test/dapr/my-dapr-app/go.sum create mode 100644 test/dapr/my-dapr-app/main.go diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml new file mode 100644 index 00000000..5703cfa7 --- /dev/null +++ b/.github/workflows/build-dapr.yaml @@ -0,0 +1,84 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build DAPR +# --------------------------------------------------------------------------- +name: CI DAPR + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.09-SNAPSHOT + go-version: + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: E2E Topics Tests + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-cluster-startup + make test-dapr + make test-cluster-shutdown + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/Makefile b/Makefile index 258859d7..afde89ea 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,8 @@ override PROTO_OUT := $(CURRDIR)/proto override BUILD_TARGETS := $(BUILD_OUTPUT)/targets override TEST_LOGS_DIR := $(BUILD_OUTPUT)/test-logs override COVERAGE_DIR := $(BUILD_OUTPUT)/coverage +override DAPR_DIR := $(BUILD_OUTPUT)/dapr-test +override DAPR_TEST_DIR := $(CURRDIR)/test/dapr override COPYRIGHT_JAR := glassfish-copyright-maven-plugin-2.4.jar override BUILD_CERTS := $(CURRDIR)/test/utils/certs override ENV_FILE := test/utils/.env @@ -467,6 +469,13 @@ test-v1-base: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e tests with Coh test-examples: test-clean gotestsum $(BUILD_PROPS) ## Run examples tests with Coherence ./scripts/run-test-examples.sh +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the tests for DAPR +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-dapr +test-dapr: test-clean gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence + ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) + # ---------------------------------------------------------------------------------------------------------------------- # Startup cluster members via docker compose # ---------------------------------------------------------------------------------------------------------------------- diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh new file mode 100755 index 00000000..6d7a18b0 --- /dev/null +++ b/scripts/run-test-dapr.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# + +if [ $# -ne 2 ]; then + echo "You must specify the dapr test directory and dapr dir to install to" + exit 1 +fi + +DAPR_TEST_DIR=$1 +DAPR_TEST_HOME=$2 + +mkdir -p $DAPR_TEST_HOME + +echo "DAPR Test Dir: $DAPR_TEST_DIR" +echo "DAPR Test Home: $DAPR_TEST_HOME" + +echo "Install DAPR" +OS=`uname` + +if [ "$OS" == "Linux" ]; then + wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash +else + echo "Assuming installed" + type dapr +fi + +echo +echo "Cloning repositories..." +cd $DAPR_TEST_HOME +rm -rf components-contrib dapr || true +git clone https://github.com/dapr/components-contrib.git +git clone https://github.com/dapr/dapr.git +cd dapr +go mod edit -replace github.com/dapr/components-contrib=../components-contrib + +# Temporary workaround until coherence in DAPR core + +cat > cmd/daprd/components/state_coherence.go < Date: Fri, 4 Jul 2025 14:03:31 +0800 Subject: [PATCH 05/44] Fic DAPR test --- .github/workflows/build-dapr.yaml | 2 +- Makefile | 2 +- scripts/run-test-dapr.sh | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml index 5703cfa7..ec326e65 100644 --- a/.github/workflows/build-dapr.yaml +++ b/.github/workflows/build-dapr.yaml @@ -67,7 +67,7 @@ jobs: with: go-version: '${{ matrix.go-version }}' - - name: E2E Topics Tests + - name: Test DAPR env: COH_VERSION: ${{ matrix.coherenceVersion }} shell: bash diff --git a/Makefile b/Makefile index afde89ea..76319d49 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ # ---------------------------------------------------------------------------------------------------------------------- # This is the version of the coherence-go-client -VERSION ?=2.3.1 +VERSION ?=2.4.0 CURRDIR := $(shell pwd) USER_ID := $(shell echo "`id -u`:`id -g`") diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 6d7a18b0..887eb348 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -29,6 +29,9 @@ else type dapr fi +dapr version +ls -l ~/.dapr + echo echo "Cloning repositories..." cd $DAPR_TEST_HOME From e4609b966a996eaeed74aac6a681077470483359 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 14:15:38 +0800 Subject: [PATCH 06/44] Add dapr init --- scripts/run-test-dapr.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 887eb348..458c94c3 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -29,6 +29,7 @@ else type dapr fi +dapr init dapr version ls -l ~/.dapr From b19b2b236e2d9562cb0a6f0ad7220ae6159938fa Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 14:43:05 +0800 Subject: [PATCH 07/44] Add the CLI to show the caches --- scripts/run-test-dapr.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 458c94c3..b848355d 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -24,6 +24,10 @@ OS=`uname` if [ "$OS" == "Linux" ]; then wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash + curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash + cohctl version + cohctl add cluster default -u http://localhost:30000/management/coherence/cluster + cohctl version else echo "Assuming installed" type dapr @@ -94,6 +98,9 @@ go mod tidy dapr run --app-id myapp --resources-path ./components/ --log-level debug -- go run main.go +# Verify the caches +cohctl get cache -o wide + From b3203dd2122a6551f39d26f0d7fb5d35e92bc190 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 14:48:39 +0800 Subject: [PATCH 08/44] Update cli address to 127.0.0.1 --- scripts/run-test-dapr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index b848355d..45a0f66e 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -26,7 +26,7 @@ if [ "$OS" == "Linux" ]; then wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash cohctl version - cohctl add cluster default -u http://localhost:30000/management/coherence/cluster + cohctl add cluster default -u http://127.0.0.1:30000/management/coherence/cluster cohctl version else echo "Assuming installed" From e011048019c9e1d2e0b924faa6003f967849425e Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 14:52:43 +0800 Subject: [PATCH 09/44] Fix dapr test script --- scripts/run-test-dapr.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 45a0f66e..12633e4a 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -6,6 +6,8 @@ # https://oss.oracle.com/licenses/upl. # +set -e + if [ $# -ne 2 ]; then echo "You must specify the dapr test directory and dapr dir to install to" exit 1 From 11a755ee6cb59c046dd479ae3637c6a363a68ba7 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 15:06:39 +0800 Subject: [PATCH 10/44] Add check to make sure coherence is up --- java/pom.xml | 2 ++ scripts/run-test-dapr.sh | 5 +++++ test/utils/docker-compose-2-members.yaml | 4 +++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/java/pom.xml b/java/pom.xml index 6f2e3c62..80efc478 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -222,6 +222,7 @@ -Dcoherence.management=all -Dcoherence.management.http=all -Dcoherence.management.http.port=30000 + -Dcoherence.health.http.port=6676 -Dcoherence.metrics.http.enabled=true -Dcoherence.metrics.http.port=9612 -Dcoherence.grpc.server.port=1408 @@ -270,6 +271,7 @@ -Dcoherence.log.level=9 -Dcoherence.management.http=all -Dcoherence.management.http.port=30000 + -Dcoherence.health.http.port=6677 -Dcoherence.management.refresh.expiry=1s -Dcoherence.distributed.localstorage=true -Dcoherence.metrics.http.enabled=true diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 12633e4a..5a260635 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -24,10 +24,15 @@ echo "DAPR Test Home: $DAPR_TEST_HOME" echo "Install DAPR" OS=`uname` +echo "Listing docker images" +docker ps + if [ "$OS" == "Linux" ]; then wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash cohctl version + # Wait for coherence + cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677 -T 120 -w cohctl add cluster default -u http://127.0.0.1:30000/management/coherence/cluster cohctl version else diff --git a/test/utils/docker-compose-2-members.yaml b/test/utils/docker-compose-2-members.yaml index 0b710fc1..37a5a8e8 100644 --- a/test/utils/docker-compose-2-members.yaml +++ b/test/utils/docker-compose-2-members.yaml @@ -1,4 +1,4 @@ -# Copyright 2023, 2024 Oracle Corporation and/or its affiliates. +# Copyright 2023, 2025 Oracle Corporation and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl. @@ -17,6 +17,7 @@ services: - 1408:1408 - 9612:9612 - 8080:8080 + - 6676:6676 volumes: - ./certs:/certs @@ -31,6 +32,7 @@ services: - COHERENCE_WKA=server1 ports: - 9613:9613 + - 6677:6677 networks: coherence: From 2abf3c15b985e2705a5b242795f71ade12f7d9f4 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 4 Jul 2025 16:10:22 +0800 Subject: [PATCH 11/44] More tests for topics --- coherence/subscriber/subscriber.go | 10 ++- coherence/topics.go | 36 ++++++++- test/e2e/topics/topics_test.go | 114 ++++++++++++++++++++++++++--- 3 files changed, 145 insertions(+), 15 deletions(-) diff --git a/coherence/subscriber/subscriber.go b/coherence/subscriber/subscriber.go index 0d388b87..765e768e 100644 --- a/coherence/subscriber/subscriber.go +++ b/coherence/subscriber/subscriber.go @@ -37,13 +37,12 @@ type ReceiveResult[V any] struct { type Options struct { SubscriberGroup *string Filter filters.Filter + Extractor []byte } // TODO: Additional options //// the optional name of the subscriber group //SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` -//// an optional Filter to filter received messages -//Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` //// an optional ValueExtractor to convert received messages //Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` //// True to return an empty value if the topic is empty @@ -62,6 +61,13 @@ func WithFilter(fltr filters.Filter) func(options *Options) { } } +// WithTransformer returns a function to set the extractor [Subscriber]. +func WithTransformer(extractor []byte) func(options *Options) { + return func(s *Options) { + s.Extractor = extractor + } +} + func (o *Options) String() string { return fmt.Sprintf("options{SubscriberGroup=%v, filter=%v}", o.SubscriberGroup, o.Filter) } diff --git a/coherence/topics.go b/coherence/topics.go index 9982455d..e2d33a65 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/extractors" "github.com/oracle/coherence-go-client/v2/coherence/publisher" "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/coherence/topic" @@ -37,6 +38,7 @@ const ( var ( _ Publisher[string] = &topicPublisher[string]{} _ NamedTopic[string] = &baseTopicsClient[string]{} + _ Subscriber[string] = &topicSubscriber[string]{} ErrTopicDestroyedOrReleased = errors.New("this topic has been destroyed or released") ErrTopicsNoSupported = errors.New("the coherence server version must support protocol version 1 or above to use topic") @@ -55,23 +57,37 @@ type NamedTopic[V any] interface { // Destroy destroys this topic on the server and releases all resources. After this operation it is no longer usable on the client or server.. Destroy(ctx context.Context) error + // CreatePublisher creates a Publisher with the specified options. CreatePublisher(ctx context.Context, options ...func(o *publisher.Options)) (Publisher[V], error) + + // CreateSubscriber creates a Subscriber with the specified options. + // Note: If you wish to create a Subscriber with a transformer, you should use the helper + // function CreatSubscriberWithTransformer. CreateSubscriber(ctx context.Context, options ...func(o *subscriber.Options)) (Subscriber[V], error) } +// Publisher provides the means to publish messages to a [NamedTopic]. type Publisher[V any] interface { GetProxyID() int32 GetPublisherID() int64 GetChannelCount() int32 + + // Publish publishes a message and returns a status. Publish(ctx context.Context, value V) (*publisher.PublishStatus, error) + + // Close closes a publisher and releases all resources associated with it in the client + // and on the server. Close(ctx context.Context) error } +// Subscriber subscribes directly to a [NamedTopic], or to a subscriber group of a [NamedTopic]. type Subscriber[V any] interface { + // Close closes a subscriber and releases all resources associated with it in the client + // and on the server. Close(ctx context.Context) error } -// GetNamedTopic gets a [NamedTopic] of the generic type specified or if a cache already exists with the +// GetNamedTopic gets a [NamedTopic] of the generic type specified or if a topic already exists with the // same type parameters, it will return it otherwise it will create a new one. func GetNamedTopic[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *topic.Options)) (NamedTopic[V], error) { var ( @@ -342,7 +358,8 @@ func ensurePublisherOptions(options ...func(cache *publisher.Options)) *publishe return publisherOptions } -// CreatePublisher creates a topic publisher. +// CreatePublisher creates a topic publisher when provided a topicName. You do not have to had created +// a topic, but it is equivalent to called CreatePublisher on a [NamedTopic]. func CreatePublisher[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *publisher.Options)) (Publisher[V], error) { publisherOptions := ensurePublisherOptions(options...) @@ -360,6 +377,21 @@ func CreatePublisher[V any](ctx context.Context, session *Session, topicName str return newPublisher[V](session, nil, result, topicName, publisherOptions) } +// CreatSubscriberWithTransformer creates a subscriber which will transform the value from the topic using +// the supplied extractor. +func CreatSubscriberWithTransformer[E any](ctx context.Context, session *Session, topicName string, + extractor extractors.ValueExtractor[any, E], options ...func(cache *subscriber.Options)) (Subscriber[E], error) { + + binExtractor, err := session.genericSerializer.Serialize(extractor) + if err != nil { + return nil, err + } + + options = append(options, subscriber.WithTransformer(binExtractor)) + + return CreateSubscriber[E](ctx, session, topicName, options...) +} + // CreateSubscriber creates a topic subscriber. func CreateSubscriber[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { var ( diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index ee18e272..a92a7d77 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -82,11 +82,7 @@ func TestBasicTopicAnonPubSub(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) - for i := 1; i <= 1_000; i++ { - status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) - g.Expect(err2).ShouldNot(gomega.HaveOccurred()) - g.Expect(status).ShouldNot(gomega.BeNil()) - } + publishEntriesString(g, pub1, 1_000) utils.Sleep(5) @@ -122,11 +118,7 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) - for i := 1; i <= 1_000; i++ { - status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) - g.Expect(err2).ShouldNot(gomega.HaveOccurred()) - g.Expect(status).ShouldNot(gomega.BeNil()) - } + publishEntriesString(g, pub1, 1_000) utils.Sleep(5) @@ -137,7 +129,7 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { err = pub1.Close(ctx) g.Expect(err).Should(gomega.HaveOccurred()) - // get teh topic so we can destroy + // get the topic so we can destroy topic1, err := coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) g.Expect(err).ShouldNot(gomega.HaveOccurred()) @@ -169,6 +161,42 @@ func TestSubscriberWithFilter(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) + runTest[string](g, topic1, sub1) +} + +func TestSubscriberWithTransformer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + topic1 coherence.NamedTopic[utils.Person] + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + // create a topic that will just return a name from the utils.Person using a transformer + topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, + subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + runTest[string](g, topic1, sub1) +} + +func runTest[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { + ctx := context.Background() + pub1, err := topic1.CreatePublisher(context.Background()) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) @@ -186,6 +214,47 @@ func TestSubscriberWithFilter(t *testing.T) { utils.Sleep(5) + err = s.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = s.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + +func TestSubscriberWithTransformerAndFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + session1 *coherence.Session + topic1 coherence.NamedTopic[utils.Person] + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer session1.Close() + + // create a topic that will just return a name from the utils.Person using a transformer + topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + publishEntriesPerson(g, pub1, 1_000) + + utils.Sleep(5) + err = sub1.Close(ctx) g.Expect(err).ShouldNot(gomega.HaveOccurred()) @@ -195,3 +264,26 @@ func TestSubscriberWithFilter(t *testing.T) { err = topic1.Destroy(ctx) g.Expect(err).ShouldNot(gomega.HaveOccurred()) } + +func publishEntriesPerson(g *gomega.WithT, pub coherence.Publisher[utils.Person], count int) { + ctx := context.Background() + for i := 1; i <= count; i++ { + p := utils.Person{ + ID: i, + Name: fmt.Sprintf("my-value-%d", i), + Age: 10 + i, + } + status, err2 := pub.Publish(ctx, p) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } +} + +func publishEntriesString(g *gomega.WithT, pub coherence.Publisher[string], count int) { + ctx := context.Background() + for i := 1; i <= count; i++ { + status, err2 := pub.Publish(ctx, fmt.Sprintf("value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } +} From f575dd26e58b69eda3f16ab7c5532bcfd13a55b6 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Sat, 5 Jul 2025 09:14:35 +0800 Subject: [PATCH 12/44] Use current coherence-go-client in dapr build --- scripts/run-test-dapr.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 5a260635..cba79a9b 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -46,15 +46,17 @@ ls -l ~/.dapr echo echo "Cloning repositories..." +DIR=`pwd` cd $DAPR_TEST_HOME rm -rf components-contrib dapr || true git clone https://github.com/dapr/components-contrib.git git clone https://github.com/dapr/dapr.git cd dapr -go mod edit -replace github.com/dapr/components-contrib=../components-contrib # Temporary workaround until coherence in DAPR core +go mod edit -replace github.com/dapr/components-contrib=../components-contrib + cat > cmd/daprd/components/state_coherence.go < Date: Sat, 5 Jul 2025 09:18:49 +0800 Subject: [PATCH 13/44] Use current coherence-go-client in dapr build --- scripts/run-test-dapr.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index cba79a9b..860ff806 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -46,7 +46,6 @@ ls -l ~/.dapr echo echo "Cloning repositories..." -DIR=`pwd` cd $DAPR_TEST_HOME rm -rf components-contrib dapr || true git clone https://github.com/dapr/components-contrib.git @@ -86,7 +85,7 @@ func init() { EOF # Test with the current go client -go mod edit -replace github.com/oracle/coherence-go-client/v2=../components-contrib/${DIR}/coherence +go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../coherence echo "Building dapr core..." From 7d925f8703f3036c364f60151b215558be808f5a Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Sat, 5 Jul 2025 09:21:58 +0800 Subject: [PATCH 14/44] Use current coherence-go-client in dapr build --- scripts/run-test-dapr.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 860ff806..f9da1126 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -32,7 +32,7 @@ if [ "$OS" == "Linux" ]; then curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash cohctl version # Wait for coherence - cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677 -T 120 -w + cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677/ -T 120 -w cohctl add cluster default -u http://127.0.0.1:30000/management/coherence/cluster cohctl version else @@ -85,7 +85,7 @@ func init() { EOF # Test with the current go client -go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../coherence +go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../../coherence echo "Building dapr core..." From b0d02d873b7f7e86b7e7d31285ad5ed783804e9f Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Sat, 5 Jul 2025 09:25:44 +0800 Subject: [PATCH 15/44] Use current coherence-go-client in dapr build --- scripts/run-test-dapr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index f9da1126..c63e21f7 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -85,7 +85,7 @@ func init() { EOF # Test with the current go client -go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../../coherence +go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../.. echo "Building dapr core..." From 630eab58f7eab7d2297d087932954f6d49cae257 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Jul 2025 12:04:47 +0800 Subject: [PATCH 16/44] Add subscriber group commands --- .github/workflows/build-dapr.yaml | 1 + coherence/subscribergroup/doc.go | 10 ++ coherence/subscribergroup/subscriber.go | 36 ++++ coherence/topics.go | 159 ++++++++++++++++- proto/topics/topic_service_messages_v1.pb.go | 2 +- test/e2e/topics/subscriber_test.go | 176 +++++++++++++++++++ test/e2e/topics/topics_test.go | 104 ----------- 7 files changed, 374 insertions(+), 114 deletions(-) create mode 100644 coherence/subscribergroup/doc.go create mode 100644 coherence/subscribergroup/subscriber.go create mode 100644 test/e2e/topics/subscriber_test.go diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml index ec326e65..c7c67bc2 100644 --- a/.github/workflows/build-dapr.yaml +++ b/.github/workflows/build-dapr.yaml @@ -26,6 +26,7 @@ jobs: matrix: coherenceVersion: - 25.09-SNAPSHOT + - 25.03.1 go-version: - 1.24.x diff --git a/coherence/subscribergroup/doc.go b/coherence/subscribergroup/doc.go new file mode 100644 index 00000000..9aeb8952 --- /dev/null +++ b/coherence/subscribergroup/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +/* +Package subscribergroup provides various subscriber group functions and types. +*/ +package subscribergroup diff --git a/coherence/subscribergroup/subscriber.go b/coherence/subscribergroup/subscriber.go new file mode 100644 index 00000000..266b6054 --- /dev/null +++ b/coherence/subscribergroup/subscriber.go @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package subscribergroup + +import ( + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence/filters" +) + +// Options provides options for creating a subscriber. +type Options struct { + Filter filters.Filter + Extractor []byte +} + +// WithFilter returns a function to set the [filters.Filter] for a [Subscriber]. +func WithFilter(fltr filters.Filter) func(options *Options) { + return func(s *Options) { + s.Filter = fltr + } +} + +// WithTransformer returns a function to set the extractor [Subscriber]. +func WithTransformer(extractor []byte) func(options *Options) { + return func(s *Options) { + s.Extractor = extractor + } +} + +func (o *Options) String() string { + return fmt.Sprintf("options{filter=%v}", o.Filter) +} diff --git a/coherence/topics.go b/coherence/topics.go index e2d33a65..0cfbd409 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -13,6 +13,7 @@ import ( "github.com/oracle/coherence-go-client/v2/coherence/extractors" "github.com/oracle/coherence-go-client/v2/coherence/publisher" "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" "github.com/oracle/coherence-go-client/v2/coherence/topic" pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" pb1 "github.com/oracle/coherence-go-client/v2/proto/v1" @@ -64,6 +65,12 @@ type NamedTopic[V any] interface { // Note: If you wish to create a Subscriber with a transformer, you should use the helper // function CreatSubscriberWithTransformer. CreateSubscriber(ctx context.Context, options ...func(o *subscriber.Options)) (Subscriber[V], error) + + // CreateSubscriberGroup creates a subscriber group with the given options. + CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error + + // DestroySubscriberGroup destroys a subscriber group. + DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error } // Publisher provides the means to publish messages to a [NamedTopic]. @@ -85,6 +92,9 @@ type Subscriber[V any] interface { // Close closes a subscriber and releases all resources associated with it in the client // and on the server. Close(ctx context.Context) error + + // DestroySubscriberGroup destroys a subscriber group created by this subscriber. + DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error } // GetNamedTopic gets a [NamedTopic] of the generic type specified or if a topic already exists with the @@ -228,6 +238,14 @@ func (bt *baseTopicsClient[V]) CreateSubscriber(ctx context.Context, options ... return CreateSubscriber[V](ctx, bt.session, bt.name, options...) } +func (bt *baseTopicsClient[V]) CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error { + return CreateSubscriberGroup(ctx, bt.session, bt.name, subscriberGroup, options...) +} + +func (bt *baseTopicsClient[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { + return DestroySubscriberGroup(ctx, bt.session, subscriberGroup) +} + func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publisher.EnsurePublisherResult, topicName string, options *publisher.Options) (Publisher[V], error) { tp := &topicPublisher[V]{ namedTopic: bt, @@ -311,6 +329,10 @@ func (ts *topicSubscriber[V]) Close(ctx context.Context) error { return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) } +func (ts *topicSubscriber[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { + return ts.session.v1StreamManagerTopics.destroySubscriberGroup(ctx, ts.proxyID, subscriberGroup) +} + func (ts *topicSubscriber[V]) isDisconnected() bool { ts.mutex.Lock() defer ts.mutex.Unlock() @@ -418,7 +440,7 @@ func CreateSubscriber[V any](ctx context.Context, session *Session, topicName st } } - result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, binFilter) + result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, subscriberOptions.SubscriberGroup, binFilter) if err != nil { return nil, err } @@ -426,6 +448,47 @@ func CreateSubscriber[V any](ctx context.Context, session *Session, topicName st return newSubscriber[V](session, nil, result, topicName, subscriberOptions) } +// CreateSubscriberGroup creates a topic subscriber group. +func CreateSubscriberGroup(ctx context.Context, session *Session, topicName string, subscriberGroup string, options ...func(cache *subscribergroup.Options)) error { + var ( + subscriberGroupOptions = &subscribergroup.Options{} + binFilter []byte + err error + ) + + // apply any subscriber options + for _, f := range options { + f(subscriberGroupOptions) + } + + if session.v1StreamManagerTopics == nil { + if err = ensureV1StreamManagerTopics(session); err != nil { + return err + } + } + + if subscriberGroupOptions.Filter != nil { + binFilter, err = session.genericSerializer.Serialize(subscriberGroupOptions.Filter) + if err != nil { + return err + } + } + + return session.v1StreamManagerTopics.ensureSubscriberGroup(ctx, topicName, subscriberGroup, binFilter) +} + +// DestroySubscriberGroup destroys a subscriber group. +func DestroySubscriberGroup(ctx context.Context, session *Session, subscriberGroup string) error { + if session.v1StreamManagerTopics == nil { + if err := ensureV1StreamManagerTopics(session); err != nil { + return err + } + } + + // TODO: Is this valid???, how can we determine the proxyID if we don't have a subscriber + return session.v1StreamManagerTopics.destroySubscriberGroup(ctx, 0, subscriberGroup) +} + // ensurePublisher ensures a publisher. func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, channelCount int32) (*publisher.EnsurePublisherResult, error) { req, err := m.newEnsurePublisherRequest(topicName, channelCount) @@ -461,8 +524,8 @@ func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, } // ensureSubscriber ensures a subscriber. -func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, binFilter []byte) (*subscriber.EnsureSubscriberResult, error) { - req, err := m.newEnsureSubscriberRequest(topicName, binFilter) +func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, subscriberGroup *string, binFilter []byte) (*subscriber.EnsureSubscriberResult, error) { + req, err := m.newEnsureSubscriberRequest(topicName, subscriberGroup, binFilter) if err != nil { return nil, err } @@ -497,6 +560,56 @@ func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string return &s, nil } +// ensureSubscriber ensures a subscriber. +func (m *streamManagerV1) ensureSubscriberGroup(ctx context.Context, topicName string, subscriberGroup string, binFilter []byte) error { + req, err := m.newEnsureSubscriberGroupRequest(topicName, subscriberGroup, binFilter) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscriberGroup) + if err != nil { + return err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + // only a complete for create subscriber group + _, err = waitForResponse(newCtx, requestType.ch, false) + + return err +} + +// destroySubscriberGroup destroys a subscriber. +func (m *streamManagerV1) destroySubscriberGroup(ctx context.Context, proxyID int32, subscriberGroup string) error { + req, err := m.newDestroySubscriberGroupRequest(proxyID, subscriberGroup) + if err != nil { + return err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_DestroySubscriberGroup) + if err != nil { + return err + } + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + // only a complete for destroy subscriber group + _, err = waitForResponse(newCtx, requestType.ch, false) + + return err +} + // destroyPublisher destroyed a publisher. func (m *streamManagerV1) destroyPublisherOrSubscriber(ctx context.Context, proxyID int32, reqType pb1topics.TopicServiceRequestType) error { req, err := m.newDestroyPublisherOrSubscriberRequest(proxyID, reqType) @@ -620,7 +733,7 @@ func (m *streamManagerV1) ensureTopicChannels(ctx context.Context, topicName str return ID, nil } -// ensure ensures a queue or cache. +// newPublishRequest publishes a message to a topic. func (m *streamManagerV1) publishToTopic(ctx context.Context, proxyID int32, publishChannel int32, value []byte) (*publisher.PublishStatus, error) { req, err := m.newPublishRequest(proxyID, publishChannel, value) @@ -729,11 +842,11 @@ func (m *streamManagerV1) newDestroyPublisherOrSubscriberRequest(proxyID int32, return m.newWrapperProxyTopicsRequest("", requestType, anyReq) } -// TODO: Add more options -func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, binFilter []byte) (*pb1.ProxyRequest, error) { +func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, subscriberGroup *string, binFilter []byte) (*pb1.ProxyRequest, error) { req := &pb1topics.EnsureSubscriberRequest{ - Topic: topicName, - Filter: binFilter, + Topic: topicName, + Filter: binFilter, + SubscriberGroup: subscriberGroup, } anyReq, err := anypb.New(req) @@ -743,6 +856,33 @@ func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, binFilter return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriber, anyReq) } +func (m *streamManagerV1) newEnsureSubscriberGroupRequest(topicName string, subscriberGroup string, binFilter []byte) (*pb1.ProxyRequest, error) { + req := &pb1topics.EnsureSubscriberGroupRequest{ + SubscriberGroup: subscriberGroup, + Filter: binFilter, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriberGroup, anyReq) +} + +func (m *streamManagerV1) newDestroySubscriberGroupRequest(proxyID int32, subscriberGroup string) (*pb1.ProxyRequest, error) { + req := &wrapperspb.StringValue{ + Value: subscriberGroup, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyPublisherRequest(proxyID, pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) + + //return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) +} + //func (m *streamManagerV1) newInitializeSubscriptionRequest(subscriptionID int64, force bool) (*pb1.ProxyRequest, error) { // req := &pb1topics.InitializeSubscriptionRequest{ // SubscriptionId: subscriptionID, @@ -817,7 +957,8 @@ func (m *streamManagerV1) newWrapperProxyTopicsRequest(topicName string, request noTopicRequired = requestType == pb1topics.TopicServiceRequestType_DestroyTopic || requestType == pb1topics.TopicServiceRequestType_EnsurePublisher || requestType == pb1topics.TopicServiceRequestType_EnsureSubscription || - requestType == pb1topics.TopicServiceRequestType_EnsureSubscriber + requestType == pb1topics.TopicServiceRequestType_EnsureSubscriber || + requestType == pb1topics.TopicServiceRequestType_DestroySubscriberGroup ) // validate the topic ID if it is not an ensure queue request diff --git a/proto/topics/topic_service_messages_v1.pb.go b/proto/topics/topic_service_messages_v1.pb.go index 2a3f3aba..3962d42b 100644 --- a/proto/topics/topic_service_messages_v1.pb.go +++ b/proto/topics/topic_service_messages_v1.pb.go @@ -90,7 +90,7 @@ const ( // The message field should be an EnsureSubscriberGroupRequest message // The response will just be a Complete message corresponding to the request id. TopicServiceRequestType_EnsureSubscriberGroup TopicServiceRequestType = 6 - // Ensure a subscriber group exists for a topic. + // Destroy a subscriber group. // The message field should be an StringValue containing the name of the // subscriber group to be destroyed // The response will just be a Complete message corresponding to the request id. diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go new file mode 100644 index 00000000..41ff40ec --- /dev/null +++ b/test/e2e/topics/subscriber_test.go @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/extractors" + "github.com/oracle/coherence-go-client/v2/coherence/filters" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" + "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" + "github.com/oracle/coherence-go-client/v2/coherence/topic" + "github.com/oracle/coherence-go-client/v2/test/utils" + "log" + "testing" + "time" +) + +func TestSubscriberWithFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-filter" + + session1, topic1 := getSessionAndTopic(g, topicName) + defer session1.Close() + + // create a subscriber with a filter + sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + runTest[string](g, topic1, sub1) +} + +func TestSubscriberWithTransformer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, topic1 := getSessionAndTopic(g, topicName) + defer session1.Close() + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, + subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + runTest[string](g, topic1, sub1) +} + +func TestSubscriberWithTransformerAndFilter(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const topicName = "my-topic-anon-transformer" + + session1, topic1 := getSessionAndTopic(g, topicName) + defer session1.Close() + + extractor := extractors.Extract[string]("name") + // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + publishEntriesPerson(g, pub1, 1_000) + + utils.Sleep(5) + + err = sub1.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = sub1.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) + + err = topic1.Destroy(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) +} + +func TestSubscriberGroupWithinTopic(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ctx = context.Background() + ) + + const ( + topicName = "my-topic-with-sg" + subGroup = "sub-group-1" + ) + + session1, topic1 := getSessionAndTopic(g, topicName) + defer session1.Close() + + err = topic1.CreateSubscriberGroup(ctx, subGroup) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subGroup)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // destroy the subscriber group from the subscriber only + g.Expect(sub1.DestroySubscriberGroup(ctx, subGroup)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub1.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) +} + +func TestSubscriberGroupWithinSubscriber(t *testing.T) { + runTestSubscriberGroup(gomega.NewWithT(t)) +} + +func TestSubscriberGroupWithinSubscriberAndFilter(t *testing.T) { + runTestSubscriberGroup(gomega.NewWithT(t), subscribergroup.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) +} + +func runTestSubscriberGroup(g *gomega.WithT, options ...func(o *subscribergroup.Options)) { + var ( + err error + ctx = context.Background() + ) + + const ( + topicName = "my-topic-with-sg" + subGroup = "sub-group-1" + ) + + session1, topic1 := getSessionAndTopic(g, topicName) + defer session1.Close() + + err = topic1.CreateSubscriberGroup(ctx, subGroup, options...) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subGroup)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // destroy the subscriber group from the subscriber only + g.Expect(sub1.DestroySubscriberGroup(ctx, subGroup)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub1.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) +} + +func getSessionAndTopic(g *gomega.WithT, topicName string) (*coherence.Session, coherence.NamedTopic[utils.Person]) { + session1, err := utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + topic1, err := coherence.GetNamedTopic[utils.Person](context.Background(), session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + return session1, topic1 +} diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index a92a7d77..0f1e690c 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -11,9 +11,6 @@ import ( "fmt" "github.com/onsi/gomega" "github.com/oracle/coherence-go-client/v2/coherence" - "github.com/oracle/coherence-go-client/v2/coherence/extractors" - "github.com/oracle/coherence-go-client/v2/coherence/filters" - "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/coherence/topic" "github.com/oracle/coherence-go-client/v2/test/utils" "log" @@ -137,63 +134,6 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) } -func TestSubscriberWithFilter(t *testing.T) { - var ( - g = gomega.NewWithT(t) - err error - session1 *coherence.Session - topic1 coherence.NamedTopic[utils.Person] - ctx = context.Background() - ) - - const topicName = "my-topic-anon-filter" - - session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - defer session1.Close() - - topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - - // create a subscriber with a filter - sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Subscriber created", sub1) - - runTest[string](g, topic1, sub1) -} - -func TestSubscriberWithTransformer(t *testing.T) { - var ( - g = gomega.NewWithT(t) - err error - session1 *coherence.Session - topic1 coherence.NamedTopic[utils.Person] - ctx = context.Background() - ) - - const topicName = "my-topic-anon-transformer" - - session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - defer session1.Close() - - // create a topic that will just return a name from the utils.Person using a transformer - topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - - extractor := extractors.Extract[string]("name") - // create a subscriber with a transformer, this - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, - subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Subscriber created", sub1) - - runTest[string](g, topic1, sub1) -} - func runTest[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { ctx := context.Background() @@ -221,50 +161,6 @@ func runTest[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], g.Expect(err).Should(gomega.HaveOccurred()) } -func TestSubscriberWithTransformerAndFilter(t *testing.T) { - var ( - g = gomega.NewWithT(t) - err error - session1 *coherence.Session - topic1 coherence.NamedTopic[utils.Person] - ctx = context.Background() - ) - - const topicName = "my-topic-anon-transformer" - - session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - defer session1.Close() - - // create a topic that will just return a name from the utils.Person using a transformer - topic1, err = coherence.GetNamedTopic[utils.Person](ctx, session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - - extractor := extractors.Extract[string]("name") - // create a subscriber with a transformer, this - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Subscriber created", sub1) - - pub1, err := topic1.CreatePublisher(context.Background()) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Publisher created", pub1) - - publishEntriesPerson(g, pub1, 1_000) - - utils.Sleep(5) - - err = sub1.Close(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - - err = sub1.Close(ctx) - g.Expect(err).Should(gomega.HaveOccurred()) - - err = topic1.Destroy(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) -} - func publishEntriesPerson(g *gomega.WithT, pub coherence.Publisher[utils.Person], count int) { ctx := context.Background() for i := 1; i <= count; i++ { From 7d63aa21eda35e24674c72c16083d7635bf7014b Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Jul 2025 15:07:13 +0800 Subject: [PATCH 17/44] Minor changes to topics --- coherence/publisher/publisher.go | 10 +----- coherence/topics.go | 1 + test/e2e/topics/subscriber_test.go | 23 +++---------- test/e2e/topics/topics_test.go | 54 ++++++++++++++++-------------- 4 files changed, 36 insertions(+), 52 deletions(-) diff --git a/coherence/publisher/publisher.go b/coherence/publisher/publisher.go index 4d99733a..69a0c486 100644 --- a/coherence/publisher/publisher.go +++ b/coherence/publisher/publisher.go @@ -16,6 +16,7 @@ import ( var ( _ OrderingOption = &OrderByDefault{} _ OrderingOption = &OrderByRoundRobin{} + //_ OrderingOption = &OrderByValue{} ) // PublishStatus provides the result of a publish operation. @@ -108,12 +109,3 @@ func (o *OrderByRoundRobin) GetPublishHash() int32 { // #nosec G115 -- val is guaranteed to be in int32 range return int32(newVal) } - -//// OrderByValue defines default ordering. -//type OrderByValue[V any] struct { -// value V -//} -// -//func (r *OrderByValue[V]) GetPublishHash() int32 { -// -//} diff --git a/coherence/topics.go b/coherence/topics.go index 0cfbd409..141ab62f 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -70,6 +70,7 @@ type NamedTopic[V any] interface { CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error // DestroySubscriberGroup destroys a subscriber group. + // TODO: Not viable to implement? DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error } diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index 41ff40ec..a2ea0212 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -14,11 +14,9 @@ import ( "github.com/oracle/coherence-go-client/v2/coherence/filters" "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" - "github.com/oracle/coherence-go-client/v2/coherence/topic" "github.com/oracle/coherence-go-client/v2/test/utils" "log" "testing" - "time" ) func TestSubscriberWithFilter(t *testing.T) { @@ -30,7 +28,7 @@ func TestSubscriberWithFilter(t *testing.T) { const topicName = "my-topic-anon-filter" - session1, topic1 := getSessionAndTopic(g, topicName) + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() // create a subscriber with a filter @@ -50,7 +48,7 @@ func TestSubscriberWithTransformer(t *testing.T) { const topicName = "my-topic-anon-transformer" - session1, topic1 := getSessionAndTopic(g, topicName) + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() extractor := extractors.Extract[string]("name") @@ -72,7 +70,7 @@ func TestSubscriberWithTransformerAndFilter(t *testing.T) { const topicName = "my-topic-anon-transformer" - session1, topic1 := getSessionAndTopic(g, topicName) + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() extractor := extractors.Extract[string]("name") @@ -111,7 +109,7 @@ func TestSubscriberGroupWithinTopic(t *testing.T) { subGroup = "sub-group-1" ) - session1, topic1 := getSessionAndTopic(g, topicName) + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() err = topic1.CreateSubscriberGroup(ctx, subGroup) @@ -147,7 +145,7 @@ func runTestSubscriberGroup(g *gomega.WithT, options ...func(o *subscribergroup. subGroup = "sub-group-1" ) - session1, topic1 := getSessionAndTopic(g, topicName) + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() err = topic1.CreateSubscriberGroup(ctx, subGroup, options...) @@ -163,14 +161,3 @@ func runTestSubscriberGroup(g *gomega.WithT, options ...func(o *subscribergroup. g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) } - -func getSessionAndTopic(g *gomega.WithT, topicName string) (*coherence.Session, coherence.NamedTopic[utils.Person]) { - session1, err := utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - - topic1, err := coherence.GetNamedTopic[utils.Person](context.Background(), session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - - return session1, topic1 -} diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index 0f1e690c..12679d89 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -11,6 +11,7 @@ import ( "fmt" "github.com/onsi/gomega" "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/publisher" "github.com/oracle/coherence-go-client/v2/coherence/topic" "github.com/oracle/coherence-go-client/v2/test/utils" "log" @@ -20,26 +21,18 @@ import ( func TestBasicTopicCreatedAndDestroy(t *testing.T) { var ( - g = gomega.NewWithT(t) - err error - session1 *coherence.Session - topic1 coherence.NamedTopic[string] - ctx = context.Background() + g = gomega.NewWithT(t) + err error + ctx = context.Background() ) const topicName = "my-topic" t.Setenv("COHERENCE_LOG_LEVEL", "ALL") - session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + session1, topic1 := getSessionAndTopic[string](g, topicName) defer session1.Close() - // get a NamedQueue with name "my-queue" - topic1, err = coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - utils.Sleep(5) err = topic1.Destroy(ctx) @@ -50,26 +43,26 @@ func TestBasicTopicCreatedAndDestroy(t *testing.T) { g.Expect(err).Should(gomega.HaveOccurred()) } -func TestBasicTopicAnonPubSub(t *testing.T) { +func TestTopicPublish(t *testing.T) { + g := gomega.NewWithT(t) + + RunTestBasicTopicAnonPubSub(g) + RunTestBasicTopicAnonPubSub(g, publisher.WithDefaultOrdering()) + RunTestBasicTopicAnonPubSub(g, publisher.WithRoundRobinOrdering()) + RunTestBasicTopicAnonPubSub(g, publisher.WithChannelCount(21)) +} + +func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publisher.Options)) { var ( - g = gomega.NewWithT(t) - err error - session1 *coherence.Session - topic1 coherence.NamedTopic[string] - ctx = context.Background() + err error + ctx = context.Background() ) const topicName = "my-topic-anon" - session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + session1, topic1 := getSessionAndTopic[string](g, topicName) defer session1.Close() - // get a NamedQueue with name "my-queue" - topic1, err = coherence.GetNamedTopic[string](ctx, session1, topicName, topic.WithChannelCount(17)) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println(topic1) - // create a subscriber first sub1, err := topic1.CreateSubscriber(ctx) g.Expect(err).ShouldNot(gomega.HaveOccurred()) @@ -183,3 +176,14 @@ func publishEntriesString(g *gomega.WithT, pub coherence.Publisher[string], coun g.Expect(status).ShouldNot(gomega.BeNil()) } } + +func getSessionAndTopic[V any](g *gomega.WithT, topicName string) (*coherence.Session, coherence.NamedTopic[V]) { + session1, err := utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + topic1, err := coherence.GetNamedTopic[V](context.Background(), session1, topicName, topic.WithChannelCount(17)) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println(topic1) + + return session1, topic1 +} From 1d7d0f6b8b2fa9605567f7c994b885b5ade744a0 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Jul 2025 15:47:28 +0800 Subject: [PATCH 18/44] Fix issue with subscriber test --- test/e2e/topics/subscriber_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index a2ea0212..b22970d7 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -127,14 +127,14 @@ func TestSubscriberGroupWithinTopic(t *testing.T) { } func TestSubscriberGroupWithinSubscriber(t *testing.T) { - runTestSubscriberGroup(gomega.NewWithT(t)) + runTestSubscriberGroup(gomega.NewWithT(t), "sub-group-1") } func TestSubscriberGroupWithinSubscriberAndFilter(t *testing.T) { - runTestSubscriberGroup(gomega.NewWithT(t), subscribergroup.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + runTestSubscriberGroup(gomega.NewWithT(t), "sub-group-2", subscribergroup.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) } -func runTestSubscriberGroup(g *gomega.WithT, options ...func(o *subscribergroup.Options)) { +func runTestSubscriberGroup(g *gomega.WithT, subscriberGroup string, options ...func(o *subscribergroup.Options)) { var ( err error ctx = context.Background() @@ -142,20 +142,19 @@ func runTestSubscriberGroup(g *gomega.WithT, options ...func(o *subscribergroup. const ( topicName = "my-topic-with-sg" - subGroup = "sub-group-1" ) session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) defer session1.Close() - err = topic1.CreateSubscriberGroup(ctx, subGroup, options...) + err = topic1.CreateSubscriberGroup(ctx, subscriberGroup, options...) g.Expect(err).ShouldNot(gomega.HaveOccurred()) - sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subGroup)) + sub1, err := topic1.CreateSubscriber(ctx, subscriber.InSubscriberGroup(subscriberGroup)) g.Expect(err).ShouldNot(gomega.HaveOccurred()) // destroy the subscriber group from the subscriber only - g.Expect(sub1.DestroySubscriberGroup(ctx, subGroup)).ShouldNot(gomega.HaveOccurred()) + g.Expect(sub1.DestroySubscriberGroup(ctx, subscriberGroup)).ShouldNot(gomega.HaveOccurred()) g.Expect(sub1.Close(ctx)).ShouldNot(gomega.HaveOccurred()) From 6f25d5e4e276472b6c8858e12aa3093f146b1181 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 10:24:51 +0800 Subject: [PATCH 19/44] Add tls tests for dapr --- .github/workflows/build-dapr-tls.yaml | 85 +++++++++++++++++++ Makefile | 7 ++ scripts/run-test-dapr.sh | 16 +++- .../my-dapr-app/components-tls/coherence.yaml | 25 ++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build-dapr-tls.yaml create mode 100644 test/dapr/my-dapr-app/components-tls/coherence.yaml diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml new file mode 100644 index 00000000..1a30aa0f --- /dev/null +++ b/.github/workflows/build-dapr-tls.yaml @@ -0,0 +1,85 @@ +# Copyright 2024, 2025 Oracle Corporation and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence Go Client GitHub Actions CI build DAPR TLS +# --------------------------------------------------------------------------- +name: CI DAPR TLS + +on: + workflow_dispatch: + push: + branches: + - '*' + schedule: + # Every day at midnight + - cron: '0 0 * * *' + +jobs: + build: + runs-on: ubuntu-22.04 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + strategy: + fail-fast: false + matrix: + coherenceVersion: + - 25.09-SNAPSHOT + - 25.03.1 + go-version: + - 1.24.x + +# Checkout the source, we need a depth of zero to fetch all of the history otherwise +# the copyright check cannot work out the date of the files from Git. + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get Docker Images + shell: bash + run: | + docker pull gcr.io/distroless/java17-debian12 + + - name: Set up JDK 17 for Build + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'zulu' + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-mods-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mods- + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + + - name: Test DAPR + env: + COH_VERSION: ${{ matrix.coherenceVersion }} + shell: bash + run: | + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup + make test-dapr + make test-cluster-shutdown + + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-output-${{ matrix.go-version }}-${{ matrix.coherenceVersion }} + path: build/_output/test-logs diff --git a/Makefile b/Makefile index 76319d49..500bb185 100644 --- a/Makefile +++ b/Makefile @@ -476,6 +476,13 @@ test-examples: test-clean gotestsum $(BUILD_PROPS) ## Run examples tests with Co test-dapr: test-clean gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) +# ---------------------------------------------------------------------------------------------------------------------- +# Executes the tests for DAPR TLS +# ---------------------------------------------------------------------------------------------------------------------- +.PHONY: test-dapr-tls +test-dapr-tls: test-clean gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence with TLS + ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) true + # ---------------------------------------------------------------------------------------------------------------------- # Startup cluster members via docker compose # ---------------------------------------------------------------------------------------------------------------------- diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index c63e21f7..c92b074c 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -8,11 +8,17 @@ set -e -if [ $# -ne 2 ]; then +if [ $# -lt 2 ]; then echo "You must specify the dapr test directory and dapr dir to install to" exit 1 fi +TLS=false + +if [ $# -eq 3 && "$3" == "tls" ]; then + TLS=true +fi + DAPR_TEST_DIR=$1 DAPR_TEST_HOME=$2 @@ -20,6 +26,7 @@ mkdir -p $DAPR_TEST_HOME echo "DAPR Test Dir: $DAPR_TEST_DIR" echo "DAPR Test Home: $DAPR_TEST_HOME" +echo "TLS: $TLS" echo "Install DAPR" OS=`uname` @@ -108,7 +115,12 @@ cd $DAPR_TEST_DIR cd my-dapr-app go mod tidy -dapr run --app-id myapp --resources-path ./components/ --log-level debug -- go run main.go +COMPONENTS=./components/ +if [ "$TLS" == "true" ]; then + COMPONENTS=./components-tls/ +fi + +dapr run --app-id myapp --resources-path $COMPONENTS --log-level debug -- go run main.go # Verify the caches cohctl get cache -o wide diff --git a/test/dapr/my-dapr-app/components-tls/coherence.yaml b/test/dapr/my-dapr-app/components-tls/coherence.yaml new file mode 100644 index 00000000..6cbe82c5 --- /dev/null +++ b/test/dapr/my-dapr-app/components-tls/coherence.yaml @@ -0,0 +1,25 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# https://oss.oracle.com/licenses/upl. +# +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.coherence + version: v1 + metadata: + - name: serverAddress + value: localhost:1408 + - name: tlsEnabled + value: true + - name: tlsClientCertPath + value: ../../utils/certs/star-lord.crt + - name: tlsClientKey + value: ../../utils/certs/star-lord.key + - name: tlsCertsPath + value: ../../utils/certs/guardians-ca.crt + - name: ignoreInvalidCerts + value: true From 410b3dccefcdcb035e6254c391376a7b05d6b79d Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 10:35:12 +0800 Subject: [PATCH 20/44] Correct tls test --- .github/workflows/build-dapr-tls.yaml | 2 +- scripts/run-test-dapr.sh | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index 1a30aa0f..5acd6a7d 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -74,7 +74,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup make test-dapr make test-cluster-shutdown diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index c92b074c..c32d97a7 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -15,7 +15,7 @@ fi TLS=false -if [ $# -eq 3 && "$3" == "tls" ]; then +if [ $# -eq 3 && "$3" == "true" ]; then TLS=true fi @@ -115,15 +115,18 @@ cd $DAPR_TEST_DIR cd my-dapr-app go mod tidy + COMPONENTS=./components/ if [ "$TLS" == "true" ]; then COMPONENTS=./components-tls/ fi +echo "Running DAPR with component $COMPONENTS" + dapr run --app-id myapp --resources-path $COMPONENTS --log-level debug -- go run main.go # Verify the caches -cohctl get cache -o wide +cohctl get caches -o wide From a5af2b7eaca8447f26d88a1dd0756187ecd4f888 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 10:45:31 +0800 Subject: [PATCH 21/44] Correct tls test --- scripts/run-test-dapr.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index c32d97a7..f1b57869 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -15,10 +15,11 @@ fi TLS=false -if [ $# -eq 3 && "$3" == "true" ]; then +if [ $# -eq 3 -a "$3" == "true" ]; then TLS=true fi + DAPR_TEST_DIR=$1 DAPR_TEST_HOME=$2 From 6582e492fa9d0a6663e1be71bc57e2176263da44 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 10:48:48 +0800 Subject: [PATCH 22/44] Correct tls test --- scripts/run-test-dapr.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index f1b57869..8ad014a6 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -13,12 +13,14 @@ if [ $# -lt 2 ]; then exit 1 fi +set -x TLS=false +echo "TLS: $3" if [ $# -eq 3 -a "$3" == "true" ]; then TLS=true fi - +set +x DAPR_TEST_DIR=$1 DAPR_TEST_HOME=$2 From df0c78cc3c3ae9198c5c416c90af5159b534d0f9 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 10:58:18 +0800 Subject: [PATCH 23/44] Correct tls test --- .github/workflows/build-dapr-tls.yaml | 2 +- scripts/run-test-dapr.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index 5acd6a7d..da83c2d7 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -75,7 +75,7 @@ jobs: run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup - make test-dapr + make test-dapr-tls make test-cluster-shutdown - uses: actions/upload-artifact@v4 diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 8ad014a6..8ca27718 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -6,6 +6,8 @@ # https://oss.oracle.com/licenses/upl. # +# TLS=true exported to enable TLS tests + set -e if [ $# -lt 2 ]; then @@ -13,14 +15,12 @@ if [ $# -lt 2 ]; then exit 1 fi -set -x TLS=false echo "TLS: $3" if [ $# -eq 3 -a "$3" == "true" ]; then TLS=true fi -set +x DAPR_TEST_DIR=$1 DAPR_TEST_HOME=$2 From cfba191a7c5fadf44064829539f72fab7b333a3c Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 11:15:42 +0800 Subject: [PATCH 24/44] Correct tls test --- .github/workflows/build-dapr-tls.yaml | 3 +-- Makefile | 4 ++-- scripts/run-test-dapr.sh | 7 ++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index da83c2d7..3e58054b 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -74,8 +74,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup - make test-dapr-tls + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup test-dapr-tls make test-cluster-shutdown - uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index 500bb185..1e7fafb3 100644 --- a/Makefile +++ b/Makefile @@ -473,14 +473,14 @@ test-examples: test-clean gotestsum $(BUILD_PROPS) ## Run examples tests with Co # Executes the tests for DAPR # ---------------------------------------------------------------------------------------------------------------------- .PHONY: test-dapr -test-dapr: test-clean gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence +test-dapr: gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) # ---------------------------------------------------------------------------------------------------------------------- # Executes the tests for DAPR TLS # ---------------------------------------------------------------------------------------------------------------------- .PHONY: test-dapr-tls -test-dapr-tls: test-clean gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence with TLS +test-dapr-tls: gotestsum $(BUILD_PROPS) ## Run dapr tests with Coherence with TLS ./scripts/run-test-dapr.sh $(DAPR_TEST_DIR) $(DAPR_DIR) true # ---------------------------------------------------------------------------------------------------------------------- diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 8ca27718..fb5aca22 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -6,7 +6,7 @@ # https://oss.oracle.com/licenses/upl. # -# TLS=true exported to enable TLS tests +# $3 = true to enable TLS test set -e @@ -16,7 +16,6 @@ if [ $# -lt 2 ]; then fi TLS=false -echo "TLS: $3" if [ $# -eq 3 -a "$3" == "true" ]; then TLS=true @@ -113,12 +112,10 @@ cp $DAPR_BIN ~/.dapr/bin echo "Running Test" -cd $DAPR_TEST_DIR +cd $DAPR_TEST_DIR/my-dapr-app -cd my-dapr-app go mod tidy - COMPONENTS=./components/ if [ "$TLS" == "true" ]; then COMPONENTS=./components-tls/ From e1856004babcef3fad5ae97306a349f341cf4f93 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 11:25:12 +0800 Subject: [PATCH 25/44] Correct tls test --- scripts/run-test-dapr.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index fb5aca22..a8e86202 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -21,6 +21,7 @@ if [ $# -eq 3 -a "$3" == "true" ]; then TLS=true fi +DIR=`pwd` DAPR_TEST_DIR=$1 DAPR_TEST_HOME=$2 @@ -41,7 +42,7 @@ if [ "$OS" == "Linux" ]; then curl -sL https://raw.githubusercontent.com/oracle/coherence-cli/main/scripts/install.sh | bash cohctl version # Wait for coherence - cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677/ -T 120 -w + cohctl monitor health -e http://127.0.0.1:6676/,http://127.0.0.1:6677/ -T 120 -w -I cohctl add cluster default -u http://127.0.0.1:30000/management/coherence/cluster cohctl version else @@ -119,6 +120,9 @@ go mod tidy COMPONENTS=./components/ if [ "$TLS" == "true" ]; then COMPONENTS=./components-tls/ + echo "DIR = $DIR" + pwd + ls -l ../../utils/certs/guardians-ca.crt fi echo "Running DAPR with component $COMPONENTS" From 63d2b417b61e24699f78cb7f7e22614b67199951 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 11:34:31 +0800 Subject: [PATCH 26/44] Correct tls test --- .github/workflows/build-dapr-tls.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index 3e58054b..1dbba3a9 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -74,7 +74,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make certs clean generate-proto generate-proto-v1 build-test-images test-cluster-startup test-dapr-tls + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,secure,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean certs generate-proto generate-proto-v1 build-test-images test-cluster-startup test-dapr-tls make test-cluster-shutdown - uses: actions/upload-artifact@v4 From 595cc8e561bb8cd34ea1000b803ca25437fdfb41 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 8 Jul 2025 12:09:02 +0800 Subject: [PATCH 27/44] Fix topics test --- test/e2e/topics/topics_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index 12679d89..6b2a228b 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -94,7 +94,7 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { ctx = context.Background() ) - const topicName = "my-topic-anon" + const topicName = "my-topic-anon-2" session1, err = utils.GetSession(coherence.WithRequestTimeout(300 * time.Second)) g.Expect(err).ShouldNot(gomega.HaveOccurred()) From 587412eaf047e09404772387f0d12d7afaa75a5a Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 10 Jul 2025 09:02:45 +0800 Subject: [PATCH 28/44] More topic events updates, update dapr test --- coherence/queue_events.go | 4 +- coherence/session.go | 9 + coherence/subscriber_events.go | 178 ++++++++++++++++++ coherence/topics.go | 110 +++++------ coherence/topics_events.go | 174 +++++++++++++++++ coherence/v1client.go | 54 +++++- .../coherence/go/testing/RestServer.java | 28 ++- scripts/run-test-dapr.sh | 32 ---- test/e2e/topics/topics_event_test.go | 134 +++++++++++++ 9 files changed, 630 insertions(+), 93 deletions(-) create mode 100644 coherence/subscriber_events.go create mode 100644 coherence/topics_events.go create mode 100644 test/e2e/topics/topics_event_test.go diff --git a/coherence/queue_events.go b/coherence/queue_events.go index 1b0284b2..7310357f 100644 --- a/coherence/queue_events.go +++ b/coherence/queue_events.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Oracle and/or its affiliates. + * Copyright (c) 2024, 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ @@ -15,7 +15,7 @@ const ( // QueueTruncated raised when a queue is truncated. QueueTruncated QueueLifecycleEventType = "queue_truncated" - // QueueReleased raised when a queue is released but the session. + // QueueReleased raised when a queue is released by the session. QueueReleased QueueLifecycleEventType = "queue_released" ) diff --git a/coherence/session.go b/coherence/session.go index c93ffa4d..19059d28 100644 --- a/coherence/session.go +++ b/coherence/session.go @@ -68,6 +68,8 @@ type Session struct { cacheIDMap safeMap[string, int32] queueIDMap safeMap[string, int32] topicIDMap safeMap[string, int32] + subscriberIDMap safeMap[int64, int32] + publisherIDMap safeMap[int64, int32] lifecycleMutex sync.RWMutex lifecycleListeners []*SessionLifecycleListener sessionConnectCtx context.Context @@ -179,6 +181,8 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( cacheIDMap: newSafeIDMap(), queueIDMap: newSafeIDMap(), topicIDMap: newSafeIDMap(), + publisherIDMap: newSafeIDMapInt64(), + subscriberIDMap: newSafeIDMapInt64(), lifecycleListeners: []*SessionLifecycleListener{}, sessOpts: &SessionOptions{ PlainText: false, @@ -409,6 +413,7 @@ func (s *Session) getCacheID(cache string) *int32 { func (s *Session) getQueueID(queue string) *int32 { return s.queueIDMap.Get(queue) } + func (s *Session) getTopicID(topic string) *int32 { return s.topicIDMap.Get(topic) } @@ -1012,3 +1017,7 @@ func (m *safeMapImpl[K, V]) Clear() { func newSafeIDMap() safeMap[string, int32] { return &safeMapImpl[string, int32]{internalMap: make(map[string]int32, 0)} } + +func newSafeIDMapInt64() safeMap[int64, int32] { + return &safeMapImpl[int64, int32]{internalMap: make(map[int64]int32, 0)} +} diff --git a/coherence/subscriber_events.go b/coherence/subscriber_events.go new file mode 100644 index 00000000..3eb8ce28 --- /dev/null +++ b/coherence/subscriber_events.go @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package coherence + +import ( + "fmt" +) + +const ( + // SubscriberDisconnected raised when a subscriber] is disconnected. + SubscriberDisconnected SubscriberLifecycleEventType = "subscriber_disconnected" + SubscriberReleased SubscriberLifecycleEventType = "subscriber_released" + SubscriberDestroyed SubscriberLifecycleEventType = "subscriber_destroyed" + SubscriberUnsubscribed SubscriberLifecycleEventType = "subscriber_unsubscribed" + SubscriberChannelHead SubscriberLifecycleEventType = "subscriber_channel_head" + SubscriberChannelPopulated SubscriberLifecycleEventType = "subscriber_channel_populated" + SubscriberChannelsLost SubscriberLifecycleEventType = "subscriber_channel_lost" + SubscriberChannelAllocation SubscriberLifecycleEventType = "subscriber_channel_allocation" + SubscriberGroupDestroyed SubscriberLifecycleEventType = "subscriber_group_destroyed" +) + +type SubscriberLifecycleEventType string + +type SubscriberLifecycleEvent[V any] interface { + Source() Subscriber[V] + Type() SubscriberLifecycleEventType +} + +type subscriberLifecycleEvent[V any] struct { + // source the source of the event + source Subscriber[V] + + // Type of this event's SubscriberLifecycleEventType + eventType SubscriberLifecycleEventType +} + +// Type returns the [SubscriberLifecycleEventType] for this [SubscriberLifecycleEvent]. +func (l *subscriberLifecycleEvent[V]) Type() SubscriberLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [SubscriberLifecycleEvent]. +func (l *subscriberLifecycleEvent[V]) Source() Subscriber[V] { + return l.source +} + +// String returns a string representation of a MapLifecycleEvent. +func (l *subscriberLifecycleEvent[V]) String() string { + return fmt.Sprintf("subscriberLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) +} + +// SubscriberLifecycleListener allows registering callbacks to be notified when lifecycle events +// occur against a [Subscriber]. +type SubscriberLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [Subscriber] event occurs. + OnAny(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [Subscriber] is destroyed. + OnDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [Subscriber] is released. + OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] +} + +// NewSubscriberLifecycleListener creates and returns a pointer to a new [SubscriberLifecycleListener] instance. +func NewSubscriberLifecycleListener[V any]() SubscriberLifecycleListener[V] { + return &subscriberLifecycleListener[V]{newEventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]]()} +} + +type subscriberLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [NamedTopic] event occurs. +func (t *subscriberLifecycleListener[V]) OnAny(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback) +} + +// OnDestroyed registers a callback that will be notified when a[NamedTopic] is destroyed. +func (t *subscriberLifecycleListener[V]) OnDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a[NamedTopic] is released. +func (t *subscriberLifecycleListener[V]) OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberReleased, callback) +} + +func (t *subscriberLifecycleListener[V]) getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [TopicLifecycleEventType]. +func (t *subscriberLifecycleListener[V]) on(event SubscriberLifecycleEventType, callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (ts *topicSubscriber[V]) AddLifecycleListener(listener SubscriberLifecycleListener[V]) error { + if ts.isClosed { + return ErrSubscriberClosed + } + + ts.addLifecycleListener(listener) + return nil +} + +func (ts *topicSubscriber[V]) RemoveLifecycleListener(listener SubscriberLifecycleListener[V]) error { + if ts.isClosed { + return ErrSubscriberClosed + } + + ts.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [SubscriberLifecycleListener]. +func (ts *topicSubscriber[V]) addLifecycleListener(listener SubscriberLifecycleListener[V]) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + for _, e := range ts.lifecycleListenersV1 { + if *e == listener { + return + } + } + ts.lifecycleListenersV1 = append(ts.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (ts *topicSubscriber[V]) removeLifecycleListener(listener SubscriberLifecycleListener[V]) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + idx := -1 + listeners := ts.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + ts.lifecycleListenersV1 = result + } +} + +func newSubscriberLifecycleEvent[V any](nt Subscriber[V], eventType SubscriberLifecycleEventType) SubscriberLifecycleEvent[V] { + return &subscriberLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generateTopicLifecycleEvent emits the queue lifecycle events. +func (ts *topicSubscriber[V]) generateSubscriberLifecycleEvent(client interface{}, eventType SubscriberLifecycleEventType) { + listeners := ts.lifecycleListenersV1 + + if sub, ok := client.(NamedTopic[V]); ok || client == nil { + event := newSubscriberLifecycleEvent[V](sub, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + // + //if eventType == TopicDestroyed { + // _ = releaseTopicInternal[V](context.Background(), bt, false) + //} + } +} + +type SubscriberEventSubmitter interface { + generateSubscriberLifecycleEvent(client interface{}, eventType SubscriberLifecycleEventType) +} diff --git a/coherence/topics.go b/coherence/topics.go index 141ab62f..03072921 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -24,15 +24,6 @@ import ( ) const ( - // TopicDestroyed raised when a queue is destroyed usually as a result of a call to NamedTopic.Destroy(). - TopicDestroyed TopicLifecycleEventType = "topic_destroyed" - // - //// QueueTruncated raised when a queue is truncated. - //QueueTruncated TopicLifecycleEventType = "queue_truncated" - - // TopicReleased raised when a topic is released but the session. - TopicReleased TopicLifecycleEventType = "topic_released" - defaultChannelCount = 17 ) @@ -48,16 +39,17 @@ var ( ErrSubscriberClosed = errors.New("subscriber has been closed and is no longer usable") ) -type TopicLifecycleEventType string - // NamedTopic defines the APIs to interact with Coherence topic allowing to publish and // subscribe from Go client. // // The type parameter is V = type of the value. type NamedTopic[V any] interface { - // Destroy destroys this topic on the server and releases all resources. After this operation it is no longer usable on the client or server.. + // Destroy destroys this topic on the server and releases all resources. After this operation it is no longer usable on the client or server. Destroy(ctx context.Context) error + // Close closes a topic and releases the resources associated with it on the client only. + Close(ctx context.Context) error + // CreatePublisher creates a Publisher with the specified options. CreatePublisher(ctx context.Context, options ...func(o *publisher.Options)) (Publisher[V], error) @@ -72,6 +64,15 @@ type NamedTopic[V any] interface { // DestroySubscriberGroup destroys a subscriber group. // TODO: Not viable to implement? DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error + + // AddLifecycleListener adds a [TopicLifecycleListener] to this topic. + AddLifecycleListener(listener TopicLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [TopicLifecycleListener] to this topic. + RemoveLifecycleListener(listener TopicLifecycleListener[V]) error + + // GetName returns the name of this topic. + GetName() string } // Publisher provides the means to publish messages to a [NamedTopic]. @@ -194,6 +195,8 @@ func (tp *topicPublisher[V]) Close(ctx context.Context) error { return ErrPublisherClosed } tp.isClosed = true + tp.session.publisherIDMap.Remove(tp.publisherID) + return tp.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, tp.proxyID, pb1topics.TopicServiceRequestType_DestroyPublisher) } @@ -208,15 +211,16 @@ func (tp *topicPublisher[V]) ensureTopicChannel() int32 { } type baseTopicsClient[V any] struct { - session *Session - valueSerializer Serializer[V] - name string - ctx context.Context - topicID int32 - isDestroyed bool - isReleased bool - mutex *sync.RWMutex - topicOpts *topic.Options + session *Session + valueSerializer Serializer[V] + name string + ctx context.Context + topicID int32 + isDestroyed bool + isReleased bool + mutex *sync.RWMutex + topicOpts *topic.Options + lifecycleListenersV1 []*TopicLifecycleListener[V] } type namedTopic[V any] struct { @@ -227,6 +231,14 @@ func (bt *baseTopicsClient[V]) Destroy(ctx context.Context) error { return releaseTopicInternal[V](ctx, bt, true) } +func (bt *baseTopicsClient[V]) GetName() string { + return bt.name +} + +func (bt *baseTopicsClient[V]) Close(ctx context.Context) error { + return releaseTopicInternal[V](ctx, bt, false) +} + func (bt *baseTopicsClient[V]) String() string { return fmt.Sprintf("topic{name=%s, topicID=%d, isReleased=%v, %v}", bt.name, bt.topicID, bt.isReleased, bt.topicOpts) } @@ -259,6 +271,8 @@ func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publ channelCount: result.ChannelCount, isClosed: false, } + + session.publisherIDMap.Add(tp.publisherID, tp.proxyID) return tp, nil } @@ -275,41 +289,26 @@ func newSubscriber[V any](session *Session, bt *baseTopicsClient[V], result *sub disconnected: true, isClosed: false, } - return ts, nil -} + session.subscriberIDMap.Add(ts.SubscriberID, ts.proxyID) -// generateQueueLifecycleEvent emits the queue lifecycle events. -func (bt *baseTopicsClient[V]) generateTopicLifecycleEvent(_ interface{}, _ TopicLifecycleEventType) { - //listeners := bq.lifecycleListenersV1 - // - //if namedQ, ok := client.(NamedQueue[V]); ok || client == nil { - // event := newQueueLifecycleEvent(namedQ, eventType) - // for _, l := range listeners { - // e := *l - // e.getEmitter().emit(eventType, event) - // } - // - // if eventType == QueueDestroyed { - // bq.session.debugConnection("received destroy for queue: %s", bq.name) - // _ = releaseInternal[V](context.Background(), bq, true) - // } - //} + return ts, nil } type topicSubscriber[V any] struct { - session *Session - namedTopic *baseTopicsClient[V] // may be nil if created outside topic - topicName string - proxyID int32 - SubscriberID int64 - SubscriberGroupID int64 - channelCount int32 - options *subscriber.Options - valueSerializer Serializer[V] - UUID string - mutex sync.RWMutex - disconnected bool - isClosed bool + session *Session + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + SubscriberID int64 + SubscriberGroupID int64 + channelCount int32 + options *subscriber.Options + valueSerializer Serializer[V] + UUID string + mutex sync.RWMutex + disconnected bool + isClosed bool + lifecycleListenersV1 []*SubscriberLifecycleListener[V] } func (ts *topicSubscriber[V]) String() string { @@ -327,6 +326,8 @@ func (ts *topicSubscriber[V]) Close(ctx context.Context) error { } ts.isClosed = true + ts.session.subscriberIDMap.Remove(ts.SubscriberID) + return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) } @@ -805,9 +806,10 @@ func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], d return err } bt.isDestroyed = true + bt.generateTopicLifecycleEvent(nil, TopicDestroyed) } else { - if existingQueue, ok := bt.session.topics[bt.name]; ok { - bt.generateTopicLifecycleEvent(existingQueue, TopicReleased) + if existingTopic, ok := bt.session.topics[bt.name]; ok { + bt.generateTopicLifecycleEvent(existingTopic, TopicReleased) bt.isReleased = true } } diff --git a/coherence/topics_events.go b/coherence/topics_events.go new file mode 100644 index 00000000..408424ee --- /dev/null +++ b/coherence/topics_events.go @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package coherence + +import ( + "context" + "fmt" +) + +const ( + // TopicDestroyed raised when a queue is destroyed usually as a result of a call to NamedTopic.Destroy(). + TopicDestroyed TopicLifecycleEventType = "topic_destroyed" + + // TopicReleased raised when a topic is released but the session. + TopicReleased TopicLifecycleEventType = "topic_released" +) + +type TopicLifecycleEventType string + +type TopicLifecycleEvent[V any] interface { + Source() NamedTopic[V] + Type() TopicLifecycleEventType +} + +type topicLifecycleEvent[V any] struct { + // source the source of the event + source NamedTopic[V] + + // Type of this event's TopicLifecycleEventType + eventType TopicLifecycleEventType +} + +// Type returns the TopicLifecycleEvent for this [TopicLifecycleEventType]. +func (l *topicLifecycleEvent[V]) Type() TopicLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [QueueLifecycleEvent]. +func (l *topicLifecycleEvent[V]) Source() NamedTopic[V] { + return l.source +} + +// String returns a string representation of a MapLifecycleEvent. +func (l *topicLifecycleEvent[V]) String() string { + return fmt.Sprintf("TopicLifecycleEvent{source=%v, type=%s}", l.Source().GetName(), l.Type()) +} + +// TopicLifecycleListener allows registering callbacks to be notified when lifecycle events +// (destroyed, released) occur against a [NamedTopic]. +type TopicLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [NamedTopic] event occurs. + OnAny(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [NamedTopic] is destroyed. + OnDestroyed(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [NamedTopic] is released. + OnReleased(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] + + getEmitter() *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] +} + +// NewTopicLifecycleListener creates and returns a pointer to a new [TopicLifecycleListener] instance. +func NewTopicLifecycleListener[V any]() TopicLifecycleListener[V] { + return &topicLifecycleListener[V]{newEventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]]()} +} + +type topicLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [NamedTopic] event occurs. +func (t *topicLifecycleListener[V]) OnAny(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback) +} + +// OnDestroyed registers a callback that will be notified when a[NamedTopic] is destroyed. +func (t *topicLifecycleListener[V]) OnDestroyed(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.on(TopicDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a[NamedTopic] is released. +func (t *topicLifecycleListener[V]) OnReleased(callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + return t.on(TopicReleased, callback) +} + +func (t *topicLifecycleListener[V]) getEmitter() *eventEmitter[TopicLifecycleEventType, TopicLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [TopicLifecycleEventType]. +func (t *topicLifecycleListener[V]) on(event TopicLifecycleEventType, callback func(TopicLifecycleEvent[V])) TopicLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (bt *baseTopicsClient[V]) AddLifecycleListener(listener TopicLifecycleListener[V]) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.addLifecycleListener(listener) + return nil +} + +func (bt *baseTopicsClient[V]) RemoveLifecycleListener(listener TopicLifecycleListener[V]) error { + if bt.isDestroyed || bt.isReleased { + return ErrTopicDestroyedOrReleased + } + + bt.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [TopicLifecycleListener]. +func (bt *baseTopicsClient[V]) addLifecycleListener(listener TopicLifecycleListener[V]) { + bt.mutex.Lock() + defer bt.mutex.Unlock() + + for _, e := range bt.lifecycleListenersV1 { + if *e == listener { + return + } + } + bt.lifecycleListenersV1 = append(bt.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (bt *baseTopicsClient[V]) removeLifecycleListener(listener TopicLifecycleListener[V]) { + bt.mutex.Lock() + defer bt.mutex.Unlock() + + idx := -1 + listeners := bt.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + bt.lifecycleListenersV1 = result + } +} + +func newTopicLifecycleEvent[V any](nt NamedTopic[V], eventType TopicLifecycleEventType) TopicLifecycleEvent[V] { + return &topicLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generateTopicLifecycleEvent emits the queue lifecycle events. +func (bt *baseTopicsClient[V]) generateTopicLifecycleEvent(client interface{}, eventType TopicLifecycleEventType) { + listeners := bt.lifecycleListenersV1 + + if namedT, ok := client.(NamedTopic[V]); ok || client == nil { + event := newTopicLifecycleEvent[V](namedT, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + + if eventType == TopicDestroyed { + _ = releaseTopicInternal[V](context.Background(), bt, false) + } + } +} + +type TopicEventSubmitter interface { + generateTopicLifecycleEvent(client interface{}, eventType TopicLifecycleEventType) +} diff --git a/coherence/v1client.go b/coherence/v1client.go index fdb0c3dd..7dd3ec18 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -69,7 +69,7 @@ func (rm responseMessage) String() string { if rm.namedCacheResponse != nil { sb.WriteString(fmt.Sprintf(", cacheId=%v", rm.namedCacheResponse.CacheId)) } else if rm.namedTopicResponse != nil { - sb.WriteString(fmt.Sprintf(", topicId=%v", rm.namedTopicResponse.ProxyId)) + sb.WriteString(fmt.Sprintf(", proxyID=%v", rm.namedTopicResponse.ProxyId)) } else if rm.namedQueueResponse != nil { sb.WriteString(fmt.Sprintf(", queueId=%v", rm.namedQueueResponse.QueueId)) } @@ -357,7 +357,11 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { return } - // process topic events here- TODO + // process topic event + if reqID == 0 && resp.namedTopicResponse != nil { + processTopicEvent(m, resp) + return + } m.session.debugConnection("id: %v Response: %v", reqID, resp) @@ -366,13 +370,56 @@ func (m *streamManagerV1) processResponse(reqID int64, resp *responseMessage) { defer m.mutex.Unlock() if e, ok := m.requests[reqID]; ok { - // request exists e.ch <- *resp } else { m.session.debugConnection("found request %v (%v) in response but no request exists", *resp, reqID) } } +// processTopicEvent processes a topic event. +func processTopicEvent(m *streamManagerV1, resp *responseMessage) { + // find the topicName from the ProxyID, this could bte the topicID, publisherID or SubscriberID + topicName := m.session.topicIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + + if topicName != nil { + // must be a topic + if existingTopic, ok := m.session.topics[*topicName]; ok { + if topicEventSubmitter, ok2 := existingTopic.(TopicEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.NamedTopicEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for topic %v: %v", topicName, err) + } else { + if eventType.Type == pb1topics.TopicEventType_TopicDestroyed { + topicEventSubmitter.generateTopicLifecycleEvent(existingTopic, TopicDestroyed) + } + } + } + } + } + return + } + + // search for publisher ID via ProxyID + publisherID := m.session.publisherIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + if publisherID != nil { + log.Printf("Received message type %v for publisher id %v. TODO\n", resp.namedTopicResponse.Type, publisherID) + return + } + + // search for subscriber ID for ProxyID + subscriberID := m.session.subscriberIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) + if subscriberID != nil { + log.Printf("Received message type %v for susbcriber id %v. TODO\n", resp.namedTopicResponse.Type, subscriberID) + return + } + + m.session.debugConnection("cannot find topic, subscriber or publisher for message ID %v and type %v", + resp.namedTopicResponse.ProxyId, resp.namedTopicResponse.Type) +} + // submitRequest submits a request to the stream manager and returns named cache request. func (m *streamManagerV1) submitRequest(req *pb1.ProxyRequest, requestType pb1.NamedCacheRequestType) (proxyRequestChannel, error) { m.mutex.Lock() @@ -416,7 +463,6 @@ func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap } else if isTopic { req, err = m.newEnsureTopicRequest(name) } else { - // cache req, err = m.newEnsureCacheRequest(name) } diff --git a/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java b/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java index 2ff58972..56ece063 100644 --- a/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java +++ b/java/coherence-go-test/src/main/java/com/oracle/coherence/go/testing/RestServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024 Oracle and/or its affiliates. + * Copyright (c) 2022, 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ @@ -10,11 +10,13 @@ import java.io.OutputStream; import java.lang.reflect.Method; import java.net.InetSocketAddress; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -30,6 +32,7 @@ import com.tangosol.net.NamedMap; import com.tangosol.net.SessionConfiguration; import com.tangosol.net.management.MBeanServerProxy; +import com.tangosol.net.topic.NamedTopic; /** * A simple Http server that is deployed into a Coherence cluster @@ -70,6 +73,7 @@ public static void main(String[] args) { server.createContext("/checkCustomerCache", RestServer::checkCustomerCache); server.createContext("/isIsReadyPresent", RestServer::isIsReadyPresent); server.createContext("/populateQueue", RestServer::populateQueue); + server.createContext("/destroyTopic", RestServer::destroyTopic); server.setExecutor(null); // creates a default executor server.start(); @@ -117,6 +121,28 @@ private static void populateQueue(HttpExchange t) throws IOException { send(t, 200, "OK"); } + private static void destroyTopic(HttpExchange t) throws IOException { + try { + URI uri = t.getRequestURI(); + String path = uri.getPath(); + String[] pathComponents = path.split("/"); + + if (pathComponents.length < 3 || !pathComponents[pathComponents.length - 2].equals("destroyTopic")) { + t.sendResponseHeaders(400, -1); // Bad Request + return; + } + + String topicName = pathComponents[pathComponents.length - 1]; + + NamedTopic topic = Coherence.clusterMember().start().get().getSession().getTopic(topicName); + topic.destroy(); + } catch (Exception e) { + e.printStackTrace(); + send(t, 404, "Error: " + e.getMessage()); + } + send(t, 200, "OK"); + } + private static void ready(HttpExchange t) throws IOException { send(t, 200, "OK"); } diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index a8e86202..4ca7badb 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -62,38 +62,6 @@ git clone https://github.com/dapr/components-contrib.git git clone https://github.com/dapr/dapr.git cd dapr -# Temporary workaround until coherence in DAPR core - -go mod edit -replace github.com/dapr/components-contrib=../components-contrib - -cat > cmd/daprd/components/state_coherence.go < Date: Thu, 10 Jul 2025 09:19:02 +0800 Subject: [PATCH 29/44] remove clone of components-contrib --- scripts/run-test-dapr.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/run-test-dapr.sh b/scripts/run-test-dapr.sh index 4ca7badb..927809c6 100755 --- a/scripts/run-test-dapr.sh +++ b/scripts/run-test-dapr.sh @@ -57,15 +57,13 @@ ls -l ~/.dapr echo echo "Cloning repositories..." cd $DAPR_TEST_HOME -rm -rf components-contrib dapr || true -git clone https://github.com/dapr/components-contrib.git +rm -rf dapr || true git clone https://github.com/dapr/dapr.git cd dapr # Test with the current go client go mod edit -replace github.com/oracle/coherence-go-client/v2=../../../.. - echo "Building dapr core..." make modtidy-all make DEBUG=1 build From 56693fc9b269cc73c02d0665e0883f95c264bf61 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 10 Jul 2025 15:09:41 +0800 Subject: [PATCH 30/44] Add initial subscriber event tests --- coherence/publisher_events.go | 198 +++++++++++++++++++++++ coherence/session.go | 4 + coherence/subscriber_events.go | 74 ++++++++- coherence/topics.go | 48 ++++-- coherence/topics_events.go | 2 +- coherence/v1client.go | 65 +++++++- test/e2e/topics/subscriber_event_test.go | 104 ++++++++++++ test/e2e/topics/subscriber_test.go | 4 +- test/e2e/topics/suite_test.go | 2 +- test/e2e/topics/topics_event_test.go | 2 +- test/e2e/topics/topics_test.go | 24 ++- 11 files changed, 501 insertions(+), 26 deletions(-) create mode 100644 coherence/publisher_events.go create mode 100644 test/e2e/topics/subscriber_event_test.go diff --git a/coherence/publisher_events.go b/coherence/publisher_events.go new file mode 100644 index 00000000..5e833fab --- /dev/null +++ b/coherence/publisher_events.go @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package coherence + +import ( + "fmt" +) + +const ( + PublisherConnected PublisherLifecycleEventType = "publisher_connected" + PublisherDisconnected PublisherLifecycleEventType = "publisher_disconnected" + PublisherReleased PublisherLifecycleEventType = "publisher_released" + PublisherDestroyed PublisherLifecycleEventType = "publisher_destroyed" + PublisherChannelsFreed PublisherLifecycleEventType = "publisher_channels_freed" +) + +type PublisherLifecycleEventType string + +type PublisherLifecycleEvent[V any] interface { + Source() Publisher[V] + Type() PublisherLifecycleEventType +} + +type publisherLifecycleEvent[V any] struct { + // source the source of the event + source Publisher[V] + + // Type of this event's PublisherLifecycleEventType + eventType PublisherLifecycleEventType +} + +// Type returns the [PublisherLifecycleEventType] for this [PublisherLifecycleEvent]. +func (l *publisherLifecycleEvent[V]) Type() PublisherLifecycleEventType { + return l.eventType +} + +// Source returns the source of this [PublisherLifecycleEvent]. +func (l *publisherLifecycleEvent[V]) Source() Publisher[V] { + return l.source +} + +// String returns a string representation of a [PublisherLifecycleEvent].. +func (l *publisherLifecycleEvent[V]) String() string { + return fmt.Sprintf("spublisherLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) +} + +// PublisherLifecycleListener allows registering callbacks to be notified when lifecycle events +// occur against a [Publisher]. +type PublisherLifecycleListener[V any] interface { + // OnAny registers a callback that will be notified when any [Publisher] event occurs. + OnAny(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnDestroyed registers a callback that will be notified when a [Publisher] is destroyed. + OnDestroyed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnReleased registers a callback that will be notified when a [Publisher] is released. + OnReleased(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnConnected registers a callback that will be notified when a [Publisher] is disconnected. + OnConnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnDisconnected registers a callback that will be notified when a [Publisher] is unsubscribed. + OnDisconnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + // OnChannelsFreed registers a callback that will be notified when a [Publisher] has channels freed. + OnChannelsFreed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] + + getEmitter() *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] +} + +// NewPublisherLifecycleListener creates and returns a pointer to a new [PublisherLifecycleListener] instance. +func NewPublisherLifecycleListener[V any]() PublisherLifecycleListener[V] { + return &publisherLifecycleListener[V]{newEventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]]()} +} + +type publisherLifecycleListener[V any] struct { //lint:ignore U1000 - required due to linter issues with generics + emitter *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] +} + +// OnAny registers a callback that will be notified when any [Publisher] event occurs. +func (t *publisherLifecycleListener[V]) OnAny(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.OnDestroyed(callback).OnReleased(callback).OnDisconnected(callback). + OnDisconnected(callback).OnChannelsFreed(callback) +} + +// OnDestroyed registers a callback that will be notified when a [SubscPublisherriber] is destroyed. +func (t *publisherLifecycleListener[V]) OnDestroyed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherDestroyed, callback) +} + +// OnReleased registers a callback that will be notified when a [Publisher] is released. +func (t *publisherLifecycleListener[V]) OnReleased(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherReleased, callback) +} + +// OnDisconnected registers a callback that will be notified when a [Publisher] is disconnected. +func (t *publisherLifecycleListener[V]) OnDisconnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherDisconnected, callback) +} + +// OnConnected registers a callback that will be notified when a [Publisher] is connected. +func (t *publisherLifecycleListener[V]) OnConnected(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherConnected, callback) +} + +// OnChannelsFreed registers a callback that will be notified when a [Publisher] has channels freed. +func (t *publisherLifecycleListener[V]) OnChannelsFreed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + return t.on(PublisherChannelsFreed, callback) +} + +func (t *publisherLifecycleListener[V]) getEmitter() *eventEmitter[PublisherLifecycleEventType, PublisherLifecycleEvent[V]] { + return t.emitter +} + +// on registers a callback for the specified [PublisherLifecycleEventType]. +func (t *publisherLifecycleListener[V]) on(event PublisherLifecycleEventType, callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { + t.emitter.on(event, callback) + return t +} + +func (tp *topicPublisher[V]) AddLifecycleListener(listener PublisherLifecycleListener[V]) error { + if tp.isClosed { + return ErrPublisherClosed + } + + tp.addLifecycleListener(listener) + return nil +} + +func (tp *topicPublisher[V]) RemoveLifecycleListener(listener PublisherLifecycleListener[V]) error { + if tp.isClosed { + return ErrPublisherClosed + } + + tp.removeLifecycleListener(listener) + return nil +} + +// addLifecycleListener adds the specified [PublisherLifecycleListener]. +func (tp *topicPublisher[V]) addLifecycleListener(listener PublisherLifecycleListener[V]) { + tp.mutex.Lock() + defer tp.mutex.Unlock() + + for _, e := range tp.lifecycleListenersV1 { + if *e == listener { + return + } + } + tp.lifecycleListenersV1 = append(tp.lifecycleListenersV1, &listener) +} + +// removeLifecycleListener removes the specified [TopicLifecycleListener]. +func (tp *topicPublisher[V]) removeLifecycleListener(listener PublisherLifecycleListener[V]) { + tp.mutex.Lock() + defer tp.mutex.Unlock() + + idx := -1 + listeners := tp.lifecycleListenersV1 + for i, c := range listeners { + if *c == listener { + idx = i + break + } + } + if idx != -1 { + result := append(listeners[:idx], listeners[idx+1:]...) + tp.lifecycleListenersV1 = result + } +} + +func newPublisherLifecycleEvent[V any](nt Publisher[V], eventType PublisherLifecycleEventType) PublisherLifecycleEvent[V] { + return &publisherLifecycleEvent[V]{source: nt, eventType: eventType} +} + +// generatePublisherLifecycleEvent emits the publisher lifecycle events. +func (tp *topicPublisher[V]) generatePublisherLifecycleEvent(client interface{}, eventType PublisherLifecycleEventType) { + listeners := tp.lifecycleListenersV1 + + if pub, ok := client.(Publisher[V]); ok || client == nil { + event := newPublisherLifecycleEvent[V](pub, eventType) + for _, l := range listeners { + e := *l + e.getEmitter().emit(eventType, event) + } + // TODO: + //if eventType == TopicDestroyed { + // _ = releaseTopicInternal[V](context.Background(), bt, false) + //} + } +} + +type PublisherEventSubmitter interface { + generatePublisherLifecycleEvent(client interface{}, eventType PublisherLifecycleEventType) +} diff --git a/coherence/session.go b/coherence/session.go index 19059d28..8b45e902 100644 --- a/coherence/session.go +++ b/coherence/session.go @@ -65,6 +65,8 @@ type Session struct { maps map[string]interface{} queues map[string]interface{} topics map[string]interface{} + publishers map[int64]interface{} + subscribers map[int64]interface{} cacheIDMap safeMap[string, int32] queueIDMap safeMap[string, int32] topicIDMap safeMap[string, int32] @@ -178,6 +180,8 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( caches: make(map[string]interface{}, 0), queues: make(map[string]interface{}, 0), topics: make(map[string]interface{}, 0), + publishers: make(map[int64]interface{}, 0), + subscribers: make(map[int64]interface{}, 0), cacheIDMap: newSafeIDMap(), queueIDMap: newSafeIDMap(), topicIDMap: newSafeIDMap(), diff --git a/coherence/subscriber_events.go b/coherence/subscriber_events.go index 3eb8ce28..0ce7870d 100644 --- a/coherence/subscriber_events.go +++ b/coherence/subscriber_events.go @@ -48,7 +48,7 @@ func (l *subscriberLifecycleEvent[V]) Source() Subscriber[V] { return l.source } -// String returns a string representation of a MapLifecycleEvent. +// String returns a string representation of a [SubscriberLifecycleEvent].\. func (l *subscriberLifecycleEvent[V]) String() string { return fmt.Sprintf("subscriberLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) } @@ -65,6 +65,27 @@ type SubscriberLifecycleListener[V any] interface { // OnReleased registers a callback that will be notified when a [Subscriber] is released. OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + // OnDisconnected registers a callback that will be notified when a [Subscriber] is disconnected. + OnDisconnected(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnUnsubscribed registers a callback that will be notified when a [Subscriber] is unsubscribed. + OnUnsubscribed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelsLost registers a callback that will be notified when a [Subscriber] loses channels. + OnChannelsLost(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelHeadChanged registers a callback that will be notified when head position of a channel has changed for a [Subscriber]. + OnChannelHeadChanged(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelPopulated registers a callback that will be notified when a channel is populated for a [Subscriber]. + OnChannelPopulated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnChannelAllocated registers a callback that will be notified when a channel is allocated for a [Subscriber]. + OnChannelAllocated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + + // OnSubscriberGroupDestroyed registers a callback that will be notified when a subscriber group is destroyed for a [Subscriber]. + OnSubscriberGroupDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] + getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] } @@ -77,21 +98,58 @@ type subscriberLifecycleListener[V any] struct { //lint:ignore U1000 - required emitter *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] } -// OnAny registers a callback that will be notified when any [NamedTopic] event occurs. +// OnAny registers a callback that will be notified when any [Subscriber] event occurs. func (t *subscriberLifecycleListener[V]) OnAny(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { - return t.OnDestroyed(callback).OnReleased(callback) + return t.OnDestroyed(callback).OnReleased(callback).OnDisconnected(callback). + OnUnsubscribed(callback).OnChannelsLost(callback).OnChannelHeadChanged(callback). + OnChannelPopulated(callback).OnSubscriberGroupDestroyed(callback).OnChannelAllocated(callback) } -// OnDestroyed registers a callback that will be notified when a[NamedTopic] is destroyed. +// OnDestroyed registers a callback that will be notified when a [Subscriber] is destroyed. func (t *subscriberLifecycleListener[V]) OnDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { return t.on(SubscriberDestroyed, callback) } -// OnReleased registers a callback that will be notified when a[NamedTopic] is released. +// OnReleased registers a callback that will be notified when a [Subscriber] is released. func (t *subscriberLifecycleListener[V]) OnReleased(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { return t.on(SubscriberReleased, callback) } +// OnDisconnected registers a callback that will be notified when a [Subscriber] is disconnected. +func (t *subscriberLifecycleListener[V]) OnDisconnected(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberDisconnected, callback) +} + +// OnUnsubscribed registers a callback that will be notified when a [Subscriber] is unsubscribed. +func (t *subscriberLifecycleListener[V]) OnUnsubscribed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberUnsubscribed, callback) +} + +// OnChannelsLost registers a callback that will be notified when a [Subscriber] loses channels. +func (t *subscriberLifecycleListener[V]) OnChannelsLost(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelsLost, callback) +} + +// OnChannelHeadChanged registers a callback that will be notified when head position of a channel has changed for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelHeadChanged(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelHead, callback) +} + +// OnChannelPopulated registers a callback that will be notified when a channel is populated for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelPopulated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelPopulated, callback) +} + +// OnChannelAllocated registers a callback that will be notified when a channel is allocated for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnChannelAllocated(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberChannelAllocation, callback) +} + +// OnSubscriberGroupDestroyed registers a callback that will be notified when a subscriber group is destroyed for a [Subscriber]. +func (t *subscriberLifecycleListener[V]) OnSubscriberGroupDestroyed(callback func(SubscriberLifecycleEvent[V])) SubscriberLifecycleListener[V] { + return t.on(SubscriberGroupDestroyed, callback) +} + func (t *subscriberLifecycleListener[V]) getEmitter() *eventEmitter[SubscriberLifecycleEventType, SubscriberLifecycleEvent[V]] { return t.emitter } @@ -156,17 +214,17 @@ func newSubscriberLifecycleEvent[V any](nt Subscriber[V], eventType SubscriberLi return &subscriberLifecycleEvent[V]{source: nt, eventType: eventType} } -// generateTopicLifecycleEvent emits the queue lifecycle events. +// generateSubscriberLifecycleEvent emits the subscriber lifecycle events. func (ts *topicSubscriber[V]) generateSubscriberLifecycleEvent(client interface{}, eventType SubscriberLifecycleEventType) { listeners := ts.lifecycleListenersV1 - if sub, ok := client.(NamedTopic[V]); ok || client == nil { + if sub, ok := client.(Subscriber[V]); ok || client == nil { event := newSubscriberLifecycleEvent[V](sub, eventType) for _, l := range listeners { e := *l e.getEmitter().emit(eventType, event) } - // + // TODO: //if eventType == TopicDestroyed { // _ = releaseTopicInternal[V](context.Background(), bt, false) //} diff --git a/coherence/topics.go b/coherence/topics.go index 03072921..d9fb6168 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -68,7 +68,7 @@ type NamedTopic[V any] interface { // AddLifecycleListener adds a [TopicLifecycleListener] to this topic. AddLifecycleListener(listener TopicLifecycleListener[V]) error - // RemoveLifecycleListener removes a [TopicLifecycleListener] to this topic. + // RemoveLifecycleListener removes a [TopicLifecycleListener] from this topic. RemoveLifecycleListener(listener TopicLifecycleListener[V]) error // GetName returns the name of this topic. @@ -97,6 +97,12 @@ type Subscriber[V any] interface { // DestroySubscriberGroup destroys a subscriber group created by this subscriber. DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error + + // AddLifecycleListener adds a [SubscriberLifecycleListener] to this subscriber. + AddLifecycleListener(listener SubscriberLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [SubscriberLifecycleListener] from this subscriber. + RemoveLifecycleListener(listener SubscriberLifecycleListener[V]) error } // GetNamedTopic gets a [NamedTopic] of the generic type specified or if a topic already exists with the @@ -152,15 +158,17 @@ func GetNamedTopic[V any](ctx context.Context, session *Session, topicName strin } type topicPublisher[V any] struct { - session *Session - isClosed bool - namedTopic *baseTopicsClient[V] // may be nil if created outside topic - topicName string - proxyID int32 - publisherID int64 - channelCount int32 - options *publisher.Options - valueSerializer Serializer[V] + session *Session + isClosed bool + namedTopic *baseTopicsClient[V] // may be nil if created outside topic + topicName string + proxyID int32 + publisherID int64 + channelCount int32 + options *publisher.Options + valueSerializer Serializer[V] + mutex *sync.RWMutex + lifecycleListenersV1 []*PublisherLifecycleListener[V] } func (tp *topicPublisher[V]) GetProxyID() int32 { @@ -197,6 +205,12 @@ func (tp *topicPublisher[V]) Close(ctx context.Context) error { tp.isClosed = true tp.session.publisherIDMap.Remove(tp.publisherID) + tp.session.mapMutex.Lock() + delete(tp.session.publishers, tp.publisherID) + + tp.session.mapMutex.Unlock() + tp.generatePublisherLifecycleEvent(tp, PublisherReleased) + return tp.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, tp.proxyID, pb1topics.TopicServiceRequestType_DestroyPublisher) } @@ -271,6 +285,9 @@ func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publ channelCount: result.ChannelCount, isClosed: false, } + session.mapMutex.Lock() + defer session.mapMutex.Unlock() + session.publishers[result.PublisherID] = tp session.publisherIDMap.Add(tp.publisherID, tp.proxyID) return tp, nil @@ -289,6 +306,11 @@ func newSubscriber[V any](session *Session, bt *baseTopicsClient[V], result *sub disconnected: true, isClosed: false, } + + session.mapMutex.Lock() + defer session.mapMutex.Unlock() + session.subscribers[result.SubscriberID] = ts + session.subscriberIDMap.Add(ts.SubscriberID, ts.proxyID) return ts, nil @@ -328,6 +350,12 @@ func (ts *topicSubscriber[V]) Close(ctx context.Context) error { ts.isClosed = true ts.session.subscriberIDMap.Remove(ts.SubscriberID) + ts.session.mapMutex.Lock() + delete(ts.session.publishers, ts.SubscriberID) + ts.session.mapMutex.Unlock() + + ts.generateSubscriberLifecycleEvent(ts, SubscriberReleased) + return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) } diff --git a/coherence/topics_events.go b/coherence/topics_events.go index 408424ee..17ae5ffa 100644 --- a/coherence/topics_events.go +++ b/coherence/topics_events.go @@ -152,7 +152,7 @@ func newTopicLifecycleEvent[V any](nt NamedTopic[V], eventType TopicLifecycleEve return &topicLifecycleEvent[V]{source: nt, eventType: eventType} } -// generateTopicLifecycleEvent emits the queue lifecycle events. +// generateTopicLifecycleEvent emits the topic lifecycle events. func (bt *baseTopicsClient[V]) generateTopicLifecycleEvent(client interface{}, eventType TopicLifecycleEventType) { listeners := bt.lifecycleListenersV1 diff --git a/coherence/v1client.go b/coherence/v1client.go index 7dd3ec18..a38375b8 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -405,14 +405,14 @@ func processTopicEvent(m *streamManagerV1, resp *responseMessage) { // search for publisher ID via ProxyID publisherID := m.session.publisherIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) if publisherID != nil { - log.Printf("Received message type %v for publisher id %v. TODO\n", resp.namedTopicResponse.Type, publisherID) + processPublisherEvent(m, resp, publisherID) return } // search for subscriber ID for ProxyID subscriberID := m.session.subscriberIDMap.KeyFromValue(resp.namedTopicResponse.ProxyId) if subscriberID != nil { - log.Printf("Received message type %v for susbcriber id %v. TODO\n", resp.namedTopicResponse.Type, subscriberID) + processSubscriberEvent(m, resp, subscriberID) return } @@ -420,6 +420,67 @@ func processTopicEvent(m *streamManagerV1, resp *responseMessage) { resp.namedTopicResponse.ProxyId, resp.namedTopicResponse.Type) } +func processPublisherEvent(m *streamManagerV1, resp *responseMessage, publisherID *int64) { + m.session.debugConnection("Received message type %v for publisher id %v", resp.namedTopicResponse.Type, *publisherID) + if existingPublisher, ok := m.session.publishers[*publisherID]; ok { + if publisherEventSubmitter, ok2 := existingPublisher.(PublisherEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.PublisherEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for publisher %v: %v", publisherID, err) + return + } + switch eventType.Type { + case pb1topics.PublisherEventType_PublisherDestroyed: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherDestroyed) + case pb1topics.PublisherEventType_PublisherConnected: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherConnected) + case pb1topics.PublisherEventType_PublisherDisconnected: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherDisconnected) + case pb1topics.PublisherEventType_PublisherChannelsFreed: + publisherEventSubmitter.generatePublisherLifecycleEvent(existingPublisher, PublisherChannelsFreed) + } + } + } + } +} +func processSubscriberEvent(m *streamManagerV1, resp *responseMessage, subscriberID *int64) { + m.session.debugConnection("Received message type %v for subscriber id %v", resp.namedTopicResponse.Type, *subscriberID) + if existingSubscriber, ok := m.session.subscribers[*subscriberID]; ok { + if subscriberEventSubmitter, ok2 := existingSubscriber.(SubscriberEventSubmitter); ok2 { + switch resp.namedTopicResponse.Type { + case pb1topics.ResponseType_Event: + var eventType = &pb1topics.SubscriberEvent{} + if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { + err = getUnmarshallError("ensure response", err) + m.session.debugConnection("cannot unmarshal topic response topic for subscriber %v: %v", subscriberID, err) + return + } + switch eventType.Type { + case pb1topics.SubscriberEventType_SubscriberChannelAllocation: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelAllocation) + case pb1topics.SubscriberEventType_SubscriberChannelHead: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelHead) + case pb1topics.SubscriberEventType_SubscriberChannelPopulated: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelPopulated) + case pb1topics.SubscriberEventType_SubscriberChannelsLost: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberChannelsLost) + case pb1topics.SubscriberEventType_SubscriberDestroyed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberDestroyed) + case pb1topics.SubscriberEventType_SubscriberDisconnected: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberDisconnected) + case pb1topics.SubscriberEventType_SubscriberGroupDestroyed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberGroupDestroyed) + case pb1topics.SubscriberEventType_SubscriberUnsubscribed: + subscriberEventSubmitter.generateSubscriberLifecycleEvent(existingSubscriber, SubscriberUnsubscribed) + } + } + } + } +} + // submitRequest submits a request to the stream manager and returns named cache request. func (m *streamManagerV1) submitRequest(req *pb1.ProxyRequest, requestType pb1.NamedCacheRequestType) (proxyRequestChannel, error) { m.mutex.Lock() diff --git a/test/e2e/topics/subscriber_event_test.go b/test/e2e/topics/subscriber_event_test.go new file mode 100644 index 00000000..8c1a4ced --- /dev/null +++ b/test/e2e/topics/subscriber_event_test.go @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/test/utils" + "sync/atomic" + "testing" + "time" +) + +func TestSubscriberEventsDestroyOnServer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ) + + const topicName = "subscriber-events" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + sub, err := topic1.CreateSubscriber(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingSubscriberListener[string](topicName) + + g.Expect(sub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestSubscriberEventsDestroyByClient(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "subscriber-events-client-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + sub, err := topic1.CreateSubscriber(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingSubscriberListener[string](topicName) + + g.Expect(sub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(sub.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(1) + + g.Eventually(func() int32 { return listener.releaseCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) +} + +func NewCountingSubscriberListener[V any](name string) *CountingSubscriberListener[V] { + countingListener := CountingSubscriberListener[V]{ + name: name, + listener: coherence.NewSubscriberLifecycleListener[V](), + } + + countingListener.listener.OnDestroyed(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.destroyCount, 1) + }).OnReleased(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnAny(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allCount, 1) + }) + + return &countingListener +} + +type CountingSubscriberListener[V any] struct { + listener coherence.SubscriberLifecycleListener[V] + name string + releaseCount int32 + destroyCount int32 + allCount int32 +} diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index b22970d7..876e7dcb 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -36,7 +36,7 @@ func TestSubscriberWithFilter(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - runTest[string](g, topic1, sub1) + runTestPerson(g, topic1, sub1) } func TestSubscriberWithTransformer(t *testing.T) { @@ -58,7 +58,7 @@ func TestSubscriberWithTransformer(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - runTest[string](g, topic1, sub1) + runTestPerson(g, topic1, sub1) } func TestSubscriberWithTransformerAndFilter(t *testing.T) { diff --git a/test/e2e/topics/suite_test.go b/test/e2e/topics/suite_test.go index c14a59dd..e03ba8e2 100644 --- a/test/e2e/topics/suite_test.go +++ b/test/e2e/topics/suite_test.go @@ -13,5 +13,5 @@ import ( // The entry point for the test suite func TestMain(m *testing.M) { - utils.RunTest(m, 1408, 30000, 8080, true) + utils.RunTest(m, 1408, 30000, 8080, false) } diff --git a/test/e2e/topics/topics_event_test.go b/test/e2e/topics/topics_event_test.go index 537268a0..e5f026d1 100644 --- a/test/e2e/topics/topics_event_test.go +++ b/test/e2e/topics/topics_event_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025 Oracle and/or its affiliates. + * Copyright (c) 2025 Oracle and/or its affiliates. * Licensed under the Universal Permissive License v 1.0 as shown at * https://oss.oracle.com/licenses/upl. */ diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index 6b2a228b..5a4a7391 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -127,7 +127,7 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) } -func runTest[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { +func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { ctx := context.Background() pub1, err := topic1.CreatePublisher(context.Background()) @@ -154,6 +154,28 @@ func runTest[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], g.Expect(err).Should(gomega.HaveOccurred()) } +func runTestString[E any](g *gomega.WithT, topic1 coherence.NamedTopic[string], s coherence.Subscriber[E]) { + ctx := context.Background() + + pub1, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + log.Println("Publisher created", pub1) + + for i := 1; i <= 1_000; i++ { + status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) + g.Expect(err2).ShouldNot(gomega.HaveOccurred()) + g.Expect(status).ShouldNot(gomega.BeNil()) + } + + utils.Sleep(5) + + err = s.Close(ctx) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = s.Close(ctx) + g.Expect(err).Should(gomega.HaveOccurred()) +} + func publishEntriesPerson(g *gomega.WithT, pub coherence.Publisher[utils.Person], count int) { ctx := context.Background() for i := 1; i <= count; i++ { From e87634dbb9334aa127ee7c297e45a47b3f93e380 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 10 Jul 2025 15:16:53 +0800 Subject: [PATCH 31/44] initial subscriber events --- coherence/topics.go | 8 +- test/e2e/topics/publisher_event_test.go | 104 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/e2e/topics/publisher_event_test.go diff --git a/coherence/topics.go b/coherence/topics.go index d9fb6168..fdb2a003 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -87,6 +87,12 @@ type Publisher[V any] interface { // Close closes a publisher and releases all resources associated with it in the client // and on the server. Close(ctx context.Context) error + + // AddLifecycleListener adds a [PublisherLifecycleListener] to this topic. + AddLifecycleListener(listener PublisherLifecycleListener[V]) error + + // RemoveLifecycleListener removes a [PublisherLifecycleListener] from this topic. + RemoveLifecycleListener(listener PublisherLifecycleListener[V]) error } // Subscriber subscribes directly to a [NamedTopic], or to a subscriber group of a [NamedTopic]. @@ -167,7 +173,7 @@ type topicPublisher[V any] struct { channelCount int32 options *publisher.Options valueSerializer Serializer[V] - mutex *sync.RWMutex + mutex sync.RWMutex lifecycleListenersV1 []*PublisherLifecycleListener[V] } diff --git a/test/e2e/topics/publisher_event_test.go b/test/e2e/topics/publisher_event_test.go new file mode 100644 index 00000000..06602c9e --- /dev/null +++ b/test/e2e/topics/publisher_event_test.go @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl. + */ + +package topics + +import ( + "context" + "github.com/onsi/gomega" + "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/test/utils" + "sync/atomic" + "testing" + "time" +) + +func TestPublisherEventsDestroyOnServer(t *testing.T) { + var ( + g = gomega.NewWithT(t) + err error + ) + + const topicName = "publisher-events" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + pub, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingPublisherListener[string](topicName) + + g.Expect(pub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + _, err = utils.IssueGetRequest(utils.GetTestContext().RestURL + "/destroyTopic/" + topicName) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + g.Eventually(func() int32 { return listener.destroyCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) + + g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) +} + +func TestPublisherEventsDestroyByClient(t *testing.T) { + var ( + g = gomega.NewWithT(t) + ctx = context.Background() + ) + + const topicName = "publisher-events-client-destroy" + + t.Setenv("COHERENCE_LOG_LEVEL", "ALL") + + session1, topic1 := getSessionAndTopic[string](g, topicName) + defer session1.Close() + + pub, err := topic1.CreatePublisher(context.Background()) + g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) + + utils.Sleep(5) + + listener := NewCountingPublisherListener[string](topicName) + + g.Expect(pub.AddLifecycleListener(listener.listener)).ShouldNot(gomega.HaveOccurred()) + + g.Expect(pub.Close(ctx)).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(1) + + g.Eventually(func() int32 { return listener.releaseCount }). + WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) +} + +func NewCountingPublisherListener[V any](name string) *CountingPublisherListener[V] { + countingListener := CountingPublisherListener[V]{ + name: name, + listener: coherence.NewPublisherLifecycleListener[V](), + } + + countingListener.listener.OnDestroyed(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.destroyCount, 1) + }).OnReleased(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnAny(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allCount, 1) + }) + + return &countingListener +} + +type CountingPublisherListener[V any] struct { + listener coherence.PublisherLifecycleListener[V] + name string + releaseCount int32 + destroyCount int32 + allCount int32 +} From 43eef6763ac296b4c44851385c14d684c0e32dff Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 15 Jul 2025 10:23:54 +0800 Subject: [PATCH 32/44] Subscriber and Publisher events progress --- .github/workflows/build-topics.yaml | 2 +- coherence/publisher_events.go | 8 ++++---- coherence/subscriber_events.go | 8 ++++---- coherence/topics.go | 8 ++++++++ test/e2e/topics/subscriber_event_test.go | 2 +- test/e2e/topics/suite_test.go | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-topics.yaml b/.github/workflows/build-topics.yaml index b2a7db53..0907a5b2 100644 --- a/.github/workflows/build-topics.yaml +++ b/.github/workflows/build-topics.yaml @@ -74,7 +74,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 INCLUDE_LONG_RUNNING=true PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics + COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=$COH_VERSION make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone-topics - uses: actions/upload-artifact@v4 if: failure() diff --git a/coherence/publisher_events.go b/coherence/publisher_events.go index 5e833fab..a4cf8555 100644 --- a/coherence/publisher_events.go +++ b/coherence/publisher_events.go @@ -7,6 +7,7 @@ package coherence import ( + "context" "fmt" ) @@ -186,10 +187,9 @@ func (tp *topicPublisher[V]) generatePublisherLifecycleEvent(client interface{}, e := *l e.getEmitter().emit(eventType, event) } - // TODO: - //if eventType == TopicDestroyed { - // _ = releaseTopicInternal[V](context.Background(), bt, false) - //} + if eventType == PublisherDestroyed || eventType == PublisherReleased { + _ = closePublisher[V](context.Background(), tp) + } } } diff --git a/coherence/subscriber_events.go b/coherence/subscriber_events.go index 0ce7870d..dd6a95f2 100644 --- a/coherence/subscriber_events.go +++ b/coherence/subscriber_events.go @@ -7,6 +7,7 @@ package coherence import ( + "context" "fmt" ) @@ -224,10 +225,9 @@ func (ts *topicSubscriber[V]) generateSubscriberLifecycleEvent(client interface{ e := *l e.getEmitter().emit(eventType, event) } - // TODO: - //if eventType == TopicDestroyed { - // _ = releaseTopicInternal[V](context.Background(), bt, false) - //} + if eventType == SubscriberDestroyed || eventType == SubscriberReleased { + _ = closeSubscriber[V](context.Background(), ts) + } } } diff --git a/coherence/topics.go b/coherence/topics.go index fdb2a003..fdc7cacb 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -205,6 +205,10 @@ func (tp *topicPublisher[V]) Publish(ctx context.Context, value V) (*publisher.P } func (tp *topicPublisher[V]) Close(ctx context.Context) error { + return closePublisher[V](ctx, tp) +} + +func closePublisher[V any](ctx context.Context, tp *topicPublisher[V]) error { if tp.isClosed { return ErrPublisherClosed } @@ -345,6 +349,10 @@ func (ts *topicSubscriber[V]) String() string { } func (ts *topicSubscriber[V]) Close(ctx context.Context) error { + return closeSubscriber[V](ctx, ts) +} + +func closeSubscriber[V any](ctx context.Context, ts *topicSubscriber[V]) error { if ts.isClosed { return ErrSubscriberClosed } diff --git a/test/e2e/topics/subscriber_event_test.go b/test/e2e/topics/subscriber_event_test.go index 8c1a4ced..1445e7a6 100644 --- a/test/e2e/topics/subscriber_event_test.go +++ b/test/e2e/topics/subscriber_event_test.go @@ -46,7 +46,7 @@ func TestSubscriberEventsDestroyOnServer(t *testing.T) { g.Eventually(func() int32 { return listener.destroyCount }). WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) - g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) + //g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) } func TestSubscriberEventsDestroyByClient(t *testing.T) { diff --git a/test/e2e/topics/suite_test.go b/test/e2e/topics/suite_test.go index e03ba8e2..c14a59dd 100644 --- a/test/e2e/topics/suite_test.go +++ b/test/e2e/topics/suite_test.go @@ -13,5 +13,5 @@ import ( // The entry point for the test suite func TestMain(m *testing.M) { - utils.RunTest(m, 1408, 30000, 8080, false) + utils.RunTest(m, 1408, 30000, 8080, true) } From 7acb67af99961c68f1aab7f83e7b5fd23b725a1c Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 15 Jul 2025 10:26:25 +0800 Subject: [PATCH 33/44] Subscriber and Publisher events progress --- test/e2e/topics/publisher_event_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/topics/publisher_event_test.go b/test/e2e/topics/publisher_event_test.go index 06602c9e..c1c0ea19 100644 --- a/test/e2e/topics/publisher_event_test.go +++ b/test/e2e/topics/publisher_event_test.go @@ -46,7 +46,7 @@ func TestPublisherEventsDestroyOnServer(t *testing.T) { g.Eventually(func() int32 { return listener.destroyCount }). WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) - g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) + //g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) } func TestPublisherEventsDestroyByClient(t *testing.T) { From 5a70c14d238b15a6cfe8761d11cbf6081bf9da4b Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 18 Jul 2025 09:00:47 +0800 Subject: [PATCH 34/44] Test and repository updates --- java/pom.xml | 13 +++++++- scripts/run-compat-ce.sh | 30 +++++++++---------- test/e2e/topics/publisher_event_test.go | 19 ++++++++---- test/e2e/topics/subscriber_event_test.go | 38 +++++++++++++++++++----- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 80efc478..e92dd955 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -292,7 +292,7 @@ ossrh-staging OSS Sonatype Staging - https://oss.sonatype.org/content/groups/staging/ + https://central.sonatype.com false @@ -303,6 +303,17 @@ snapshots-repo + https://central.sonatype.com/repository/maven-snapshots + + false + + + true + + + + + snapshots-repo-old https://oss.sonatype.org/content/repositories/snapshots false diff --git a/scripts/run-compat-ce.sh b/scripts/run-compat-ce.sh index 1485d63b..cfafadf7 100755 --- a/scripts/run-compat-ce.sh +++ b/scripts/run-compat-ce.sh @@ -1,7 +1,7 @@ #!/bin/bash # -# Copyright (c) 2022, 2024 Oracle and/or its affiliates. +# Copyright (c) 2022, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at # https://oss.oracle.com/licenses/upl. # @@ -12,40 +12,40 @@ set -e # Set the following to include long running streaming tests # INCLUDE_LONG_RUNNING=true -echo "Coherence CE 22.06.12" -COHERENCE_VERSION=22.06.12 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-standalone +echo "Coherence CE 22.06.13" +COHERENCE_VERSION=22.06.13 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-standalone -echo "Coherence CE 14.1.2-0-2" -COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 COHERENCE_VERSION=14.1.2-0-2 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-standalone +echo "Coherence CE 14.1.2-0-3" +COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 COHERENCE_VERSION=14.1.2-0-3 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-standalone -echo "Coherence CE 14.1.2-0-2 Streaming" -COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 COHERENCE_VERSION=14.1.2-0-2 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-streaming +echo "Coherence CE 14.1.2-0-3 Streaming" +COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 COHERENCE_VERSION=14.1.2-0-3 PROFILES=,-jakarta,javax make clean generate-proto build-test-images test-e2e-streaming -echo "Coherence CE 22.06.12 with scope" -COHERENCE_VERSION=22.06.12 PROFILES=,-jakarta,javax,scope make clean generate-proto build-test-images test-e2e-standalone-scope +echo "Coherence CE 22.06.13 with scope" +COHERENCE_VERSION=22.06.13 PROFILES=,-jakarta,javax,scope make clean generate-proto build-test-images test-e2e-standalone-scope -echo "Coherence CE 22.06.12 with SSL using env" +echo "Coherence CE 22.06.13 with SSL using env" SECURE=env COHERENCE_IGNORE_INVALID_CERTS=true \ COHERENCE_TLS_CERTS_PATH=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.7 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone + COHERENCE_VERSION=22.06.3 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone -echo "Coherence CE 22.06.12 with SSL using options" +echo "Coherence CE 22.06.13 with SSL using options" # suite_test.go takes the below env vars and then populates the session options SECURE=options COHERENCE_IGNORE_INVALID_CERTS_OPTION=true \ COHERENCE_TLS_CERTS_PATH_OPTION=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT_OPTION=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY_OPTION=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.7 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone + COHERENCE_VERSION=22.06.13 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone -echo "Coherence CE 22.06.12 with SSL using tlsConfig" +echo "Coherence CE 22.06.13 with SSL using tlsConfig" # suite_test.go takes the below env vars and then creates a tls.Config and passes to the WithTLSConfig SECURE=tlsConfig COHERENCE_IGNORE_INVALID_CERTS_OPTION=true \ COHERENCE_TLS_CERTS_PATH_OPTION=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT_OPTION=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY_OPTION=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.7 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone + COHERENCE_VERSION=22.06.13 PROFILES=,secure make clean certs generate-proto build-test-images test-e2e-standalone echo "Coherence CE 25.03.1" COHERENCE_BASE_IMAGE=gcr.io/distroless/java17-debian12 PROFILES=,jakarta,-javax COHERENCE_VERSION=25.03.1 make clean generate-proto generate-proto-v1 build-test-images test-e2e-standalone diff --git a/test/e2e/topics/publisher_event_test.go b/test/e2e/topics/publisher_event_test.go index c1c0ea19..2c22421d 100644 --- a/test/e2e/topics/publisher_event_test.go +++ b/test/e2e/topics/publisher_event_test.go @@ -88,6 +88,12 @@ func NewCountingPublisherListener[V any](name string) *CountingPublisherListener atomic.AddInt32(&countingListener.destroyCount, 1) }).OnReleased(func(_ coherence.PublisherLifecycleEvent[V]) { atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnConnected(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.connectedCount, 1) + }).OnDisconnected(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.disconnectedCount, 1) + }).OnChannelsFreed(func(_ coherence.PublisherLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.freedCount, 1) }).OnAny(func(_ coherence.PublisherLifecycleEvent[V]) { atomic.AddInt32(&countingListener.allCount, 1) }) @@ -96,9 +102,12 @@ func NewCountingPublisherListener[V any](name string) *CountingPublisherListener } type CountingPublisherListener[V any] struct { - listener coherence.PublisherLifecycleListener[V] - name string - releaseCount int32 - destroyCount int32 - allCount int32 + listener coherence.PublisherLifecycleListener[V] + name string + releaseCount int32 + destroyCount int32 + connectedCount int32 + disconnectedCount int32 + freedCount int32 + allCount int32 } diff --git a/test/e2e/topics/subscriber_event_test.go b/test/e2e/topics/subscriber_event_test.go index 1445e7a6..b26493d6 100644 --- a/test/e2e/topics/subscriber_event_test.go +++ b/test/e2e/topics/subscriber_event_test.go @@ -10,6 +10,7 @@ import ( "context" "github.com/onsi/gomega" "github.com/oracle/coherence-go-client/v2/coherence" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/test/utils" "sync/atomic" "testing" @@ -22,14 +23,17 @@ func TestSubscriberEventsDestroyOnServer(t *testing.T) { err error ) - const topicName = "subscriber-events" + const ( + topicName = "subscriber-events" + subscriberGroupName = "subscriber-events" + ) t.Setenv("COHERENCE_LOG_LEVEL", "ALL") session1, topic1 := getSessionAndTopic[string](g, topicName) defer session1.Close() - sub, err := topic1.CreateSubscriber(context.Background()) + sub, err := topic1.CreateSubscriber(context.Background(), subscriber.InSubscriberGroup(subscriberGroupName)) g.Expect(err).Should(gomega.Not(gomega.HaveOccurred())) utils.Sleep(5) @@ -43,7 +47,7 @@ func TestSubscriberEventsDestroyOnServer(t *testing.T) { utils.Sleep(5) - g.Eventually(func() int32 { return listener.destroyCount }). + g.Eventually(func() int32 { return listener.subscriberGrpCount }). WithTimeout(10 * time.Second).Should(gomega.Equal(int32(1))) //g.Expect(topic1.Close(context.Background())).Should(gomega.Equal(coherence.ErrTopicDestroyedOrReleased)) @@ -88,6 +92,18 @@ func NewCountingSubscriberListener[V any](name string) *CountingSubscriberListen atomic.AddInt32(&countingListener.destroyCount, 1) }).OnReleased(func(_ coherence.SubscriberLifecycleEvent[V]) { atomic.AddInt32(&countingListener.releaseCount, 1) + }).OnDisconnected(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.disconnectedCount, 1) + }).OnUnsubscribed(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.unsubscribedCount, 1) + }).OnChannelPopulated(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.populatedCount, 1) + }).OnChannelAllocated(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.allocatedCount, 1) + }).OnChannelHeadChanged(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.headChangedCount, 1) + }).OnSubscriberGroupDestroyed(func(_ coherence.SubscriberLifecycleEvent[V]) { + atomic.AddInt32(&countingListener.subscriberGrpCount, 1) }).OnAny(func(_ coherence.SubscriberLifecycleEvent[V]) { atomic.AddInt32(&countingListener.allCount, 1) }) @@ -96,9 +112,15 @@ func NewCountingSubscriberListener[V any](name string) *CountingSubscriberListen } type CountingSubscriberListener[V any] struct { - listener coherence.SubscriberLifecycleListener[V] - name string - releaseCount int32 - destroyCount int32 - allCount int32 + listener coherence.SubscriberLifecycleListener[V] + name string + releaseCount int32 + disconnectedCount int32 + unsubscribedCount int32 + populatedCount int32 + allocatedCount int32 + headChangedCount int32 + subscriberGrpCount int32 + destroyCount int32 + allCount int32 } From cb3411344d0f5aa503d1a493edf5316a3b2cede9 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Fri, 18 Jul 2025 10:26:31 +0800 Subject: [PATCH 35/44] Further test updates --- test/e2e/topics/subscriber_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index 876e7dcb..84f19ffc 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -141,7 +141,7 @@ func runTestSubscriberGroup(g *gomega.WithT, subscriberGroup string, options ... ) const ( - topicName = "my-topic-with-sg" + topicName = "my-topic-with-sg-2" ) session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) From 6abee30ec44df2160e40d70ccf47151a475d1643 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 21 Jul 2025 08:16:48 +0800 Subject: [PATCH 36/44] Update CE versions --- .github/workflows/build-compatability-1412.yaml | 4 ++-- .github/workflows/build-compatability-2206.yaml | 4 ++-- .github/workflows/build-compatability-v1-1412.yaml | 4 ++-- .github/workflows/build-compatability-v1.yaml | 2 +- .github/workflows/build-compatability.yaml | 2 +- .github/workflows/build-dapr-tls.yaml | 2 +- .github/workflows/build-dapr.yaml | 2 +- .github/workflows/build-perf.yaml | 4 ++-- .github/workflows/build-queues-1412.yaml | 4 ++-- .github/workflows/build-queues.yaml | 4 ++-- .github/workflows/build-v1.yaml | 4 ++-- .github/workflows/build.yaml | 10 +++++----- .github/workflows/discovery-compatability-tests.yaml | 10 +++++----- .github/workflows/examples-jakarta-v1.2.2.yaml | 4 ++-- .github/workflows/examples-jakarta.yaml | 4 ++-- .github/workflows/examples-v1.2.2.yaml | 4 ++-- .github/workflows/examples.yaml | 8 ++++---- .../resolver-clusters-compatability-tests.yaml | 6 +++--- .github/workflows/resolver-compatability-tests.yaml | 6 +++--- .github/workflows/streaming-jakarta.yaml | 4 ++-- .github/workflows/streaming.yaml | 8 ++++---- 21 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build-compatability-1412.yaml b/.github/workflows/build-compatability-1412.yaml index 4a271a79..e3c4a0b9 100644 --- a/.github/workflows/build-compatability-1412.yaml +++ b/.github/workflows/build-compatability-1412.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 14.1.2-0-3 + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-compatability-2206.yaml b/.github/workflows/build-compatability-2206.yaml index 9e813970..1646e525 100644 --- a/.github/workflows/build-compatability-2206.yaml +++ b/.github/workflows/build-compatability-2206.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.12 - - 22.06.13-SNAPSHOT + - 22.06.13 + - 22.06.14-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-compatability-v1-1412.yaml b/.github/workflows/build-compatability-v1-1412.yaml index 236e993b..b8884ea8 100644 --- a/.github/workflows/build-compatability-v1-1412.yaml +++ b/.github/workflows/build-compatability-v1-1412.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 14.1.2-0-3 + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-compatability-v1.yaml b/.github/workflows/build-compatability-v1.yaml index f470594b..be254f09 100644 --- a/.github/workflows/build-compatability-v1.yaml +++ b/.github/workflows/build-compatability-v1.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03 - 25.03.1 + - 25.03.2 go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-compatability.yaml b/.github/workflows/build-compatability.yaml index 8bac66fa..40de296f 100644 --- a/.github/workflows/build-compatability.yaml +++ b/.github/workflows/build-compatability.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03 - 25.03.1 + - 25.03.2 go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index 1dbba3a9..5d40a17b 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -26,7 +26,7 @@ jobs: matrix: coherenceVersion: - 25.09-SNAPSHOT - - 25.03.1 + - 25.03.3 go-version: - 1.24.x diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml index c7c67bc2..9e223b9e 100644 --- a/.github/workflows/build-dapr.yaml +++ b/.github/workflows/build-dapr.yaml @@ -26,7 +26,7 @@ jobs: matrix: coherenceVersion: - 25.09-SNAPSHOT - - 25.03.1 + - 25.03.2 go-version: - 1.24.x diff --git a/.github/workflows/build-perf.yaml b/.github/workflows/build-perf.yaml index b9bcd338..a4ca1145 100644 --- a/.github/workflows/build-perf.yaml +++ b/.github/workflows/build-perf.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 14.1.2-0-2 - - 25.03.1 + - 14.1.2-0-3 + - 25.03.2 go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-queues-1412.yaml b/.github/workflows/build-queues-1412.yaml index 4be2d590..cb73d5ac 100644 --- a/.github/workflows/build-queues-1412.yaml +++ b/.github/workflows/build-queues-1412.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 14.1.2-0-3 + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-queues.yaml b/.github/workflows/build-queues.yaml index 5fe890db..8d0faf18 100644 --- a/.github/workflows/build-queues.yaml +++ b/.github/workflows/build-queues.yaml @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03.1 - - 25.03.2-SNAPSHOT + - 25.03.2 + - 25.03.3-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/build-v1.yaml b/.github/workflows/build-v1.yaml index defc9e5b..1ffd9120 100644 --- a/.github/workflows/build-v1.yaml +++ b/.github/workflows/build-v1.yaml @@ -25,9 +25,9 @@ jobs: - 1.23.x - 1.24.x coherence-version: - - 25.03 - 25.03.1 - - 25.03.2-SNAPSHOT + - 25.03.2 + - 25.03.3-SNAPSHOT # Checkout the source, we need a depth of zero to fetch all of the history otherwise # the copyright check cannot work out the date of the files from Git. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index be349eb0..6a58af73 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -68,7 +68,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - INCLUDE_LONG_RUNNING=true PROFILES=,-jakarta,javax COHERENCE_VERSION=22.06.12 make clean generate-proto build-test-images test-e2e-standalone + INCLUDE_LONG_RUNNING=true PROFILES=,-jakarta,javax COHERENCE_VERSION=22.06.13 make clean generate-proto build-test-images test-e2e-standalone - name: Profile Near Cache shell: bash @@ -85,7 +85,7 @@ jobs: shell: bash run: | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 - SKIP_PROTO_GENERATION=true COHERENCE_VERSION=22.06.12 PROFILES=,-jakarta,javax,scope make clean generate-proto build-test-images test-e2e-standalone-scope + SKIP_PROTO_GENERATION=true COHERENCE_VERSION=22.06.13 PROFILES=,-jakarta,javax,scope make clean generate-proto build-test-images test-e2e-standalone-scope - uses: actions/upload-artifact@v4 if: failure() @@ -100,7 +100,7 @@ jobs: COHERENCE_TLS_CERTS_PATH=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.12 PROFILES=,secure,-jakarta,javax make clean certs generate-proto build-test-images test-e2e-standalone + COHERENCE_VERSION=22.06.13 PROFILES=,secure,-jakarta,javax make clean certs generate-proto build-test-images test-e2e-standalone - name: E2E Local Tests SSL (options) shell: bash @@ -109,7 +109,7 @@ jobs: COHERENCE_TLS_CERTS_PATH_OPTION=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT_OPTION=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY_OPTION=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.12 PROFILES=,secure,-jakarta,javax,scope make clean certs generate-proto build-test-images test-e2e-standalone-scope + COHERENCE_VERSION=22.06.13 PROFILES=,secure,-jakarta,javax,scope make clean certs generate-proto build-test-images test-e2e-standalone-scope - name: E2E Local Tests SSL (tlsConfig) shell: bash @@ -118,7 +118,7 @@ jobs: COHERENCE_TLS_CERTS_PATH_OPTION=`pwd`/test/utils/certs/guardians-ca.crt \ COHERENCE_TLS_CLIENT_CERT_OPTION=`pwd`/test/utils/certs/star-lord.crt \ COHERENCE_TLS_CLIENT_KEY_OPTION=`pwd`/test/utils/certs/star-lord.key \ - COHERENCE_VERSION=22.06.12 PROFILES=,secure,-jakarta,javax,scope make clean certs generate-proto build-test-images test-e2e-standalone-scope + COHERENCE_VERSION=22.06.13 PROFILES=,secure,-jakarta,javax,scope make clean certs generate-proto build-test-images test-e2e-standalone-scope - uses: actions/upload-artifact@v4 if: failure() diff --git a/.github/workflows/discovery-compatability-tests.yaml b/.github/workflows/discovery-compatability-tests.yaml index 312e030e..b9003b32 100644 --- a/.github/workflows/discovery-compatability-tests.yaml +++ b/.github/workflows/discovery-compatability-tests.yaml @@ -22,11 +22,11 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.12 - - 22.06.13-SNAPSHOT - - 25.03.1 - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 22.06.13 + - 22.06.14-SNAPSHOT + - 25.03.2 + - 14.1.2-0-3 + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/examples-jakarta-v1.2.2.yaml b/.github/workflows/examples-jakarta-v1.2.2.yaml index cb2451d6..20fd3202 100644 --- a/.github/workflows/examples-jakarta-v1.2.2.yaml +++ b/.github/workflows/examples-jakarta-v1.2.2.yaml @@ -23,9 +23,9 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03 - 25.03.1 - - 25.03.2-SNAPSHOT + - 25.03.2 + - 25.03.3-SNAPSHOT go-version: - 1.21.x - 1.22.x diff --git a/.github/workflows/examples-jakarta.yaml b/.github/workflows/examples-jakarta.yaml index f8a9029f..442f9e2b 100644 --- a/.github/workflows/examples-jakarta.yaml +++ b/.github/workflows/examples-jakarta.yaml @@ -22,8 +22,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03.1 - - 25.03.2-SNAPSHOT + - 25.03.2 + - 25.03.3-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/examples-v1.2.2.yaml b/.github/workflows/examples-v1.2.2.yaml index ce88ee0d..4e149aa6 100644 --- a/.github/workflows/examples-v1.2.2.yaml +++ b/.github/workflows/examples-v1.2.2.yaml @@ -23,8 +23,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.13-SNAPSHOT - - 22.06.12 + - 22.06.14-SNAPSHOT + - 22.06.13 go-version: - 1.21.x - 1.22.x diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 344dc322..23c11202 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -22,10 +22,10 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.13-SNAPSHOT - - 22.06.12 - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 22.06.14-SNAPSHOT + - 22.06.13 + - 14.1.2-0-3 + - 14.1.2-0-2-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/resolver-clusters-compatability-tests.yaml b/.github/workflows/resolver-clusters-compatability-tests.yaml index d2c9d2d2..4a915921 100644 --- a/.github/workflows/resolver-clusters-compatability-tests.yaml +++ b/.github/workflows/resolver-clusters-compatability-tests.yaml @@ -22,9 +22,9 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.12 - - 25.03.1 - - 14.1.2-0-2 + - 22.06.13 + - 25.03.2 + - 14.1.2-0-3 go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/resolver-compatability-tests.yaml b/.github/workflows/resolver-compatability-tests.yaml index a442b2c8..0a4411fe 100644 --- a/.github/workflows/resolver-compatability-tests.yaml +++ b/.github/workflows/resolver-compatability-tests.yaml @@ -22,9 +22,9 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.12 - - 25.03.1 - - 14.1.2-0-2 + - 22.06.13 + - 25.03.2 + - 14.1.2-0-3 go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/streaming-jakarta.yaml b/.github/workflows/streaming-jakarta.yaml index 246b2853..ce4e489d 100644 --- a/.github/workflows/streaming-jakarta.yaml +++ b/.github/workflows/streaming-jakarta.yaml @@ -22,8 +22,8 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 25.03.1 - - 25.03.2-SNAPSHOT + - 25.03.2 + - 25.03.3-SNAPSHOT go-version: - 1.23.x - 1.24.x diff --git a/.github/workflows/streaming.yaml b/.github/workflows/streaming.yaml index 50d23b7a..8d25178a 100644 --- a/.github/workflows/streaming.yaml +++ b/.github/workflows/streaming.yaml @@ -22,10 +22,10 @@ jobs: fail-fast: false matrix: coherenceVersion: -# - 22.06.13-SNAPSHOT -# - 22.06.12 - - 14.1.2-0-2 - - 14.1.2-0-3-SNAPSHOT + - 22.06.14-SNAPSHOT + - 22.06.13 + - 14.1.2-0-3 + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x From 97eb0d0ffc9b7765eacccaf4f38fbb281dc9c1c4 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 21 Jul 2025 08:44:28 +0800 Subject: [PATCH 37/44] Fix dapr action --- .github/workflows/build-dapr-tls.yaml | 3 ++- .github/workflows/build-dapr.yaml | 1 + README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-dapr-tls.yaml b/.github/workflows/build-dapr-tls.yaml index 5d40a17b..14984175 100644 --- a/.github/workflows/build-dapr-tls.yaml +++ b/.github/workflows/build-dapr-tls.yaml @@ -26,7 +26,8 @@ jobs: matrix: coherenceVersion: - 25.09-SNAPSHOT - - 25.03.3 + - 25.03.3-SNAPSHOT + - 25.03.2 go-version: - 1.24.x diff --git a/.github/workflows/build-dapr.yaml b/.github/workflows/build-dapr.yaml index 9e223b9e..9a8d4398 100644 --- a/.github/workflows/build-dapr.yaml +++ b/.github/workflows/build-dapr.yaml @@ -27,6 +27,7 @@ jobs: coherenceVersion: - 25.09-SNAPSHOT - 25.03.2 + - 25.03.3-SNAPSHOT go-version: - 1.24.x diff --git a/README.md b/README.md index c8a768e4..30590747 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ For local development, we recommend using the Coherence CE Docker image; it cont everything necessary for the client to operate correctly. ```bash -docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:25.03.1 +docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:25.03.2 ``` ## Installation From 9c08dfed9be765e7cf2c2ca64b8fb4f1adbc2655 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 21 Jul 2025 08:45:39 +0800 Subject: [PATCH 38/44] Fix examples action --- .github/workflows/examples.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 23c11202..006f63a2 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -25,7 +25,7 @@ jobs: - 22.06.14-SNAPSHOT - 22.06.13 - 14.1.2-0-3 - - 14.1.2-0-2-SNAPSHOT + - 14.1.2-0-4-SNAPSHOT go-version: - 1.23.x - 1.24.x From cd757596fd74d9ae2975deeda6a33bcaf476e4e8 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 29 Jul 2025 10:36:41 +0800 Subject: [PATCH 39/44] Topics progress - receive and commit --- Makefile | 6 +- coherence/subscriber/subscriber.go | 36 +- coherence/topics.go | 273 +++++++++--- proto/topics/topic_service_messages_v1.pb.go | 445 ++++++++++++++----- test/e2e/standalone/event_test.go | 1 - test/e2e/topics/subscriber_event_test.go | 2 + test/e2e/topics/subscriber_test.go | 17 +- test/e2e/topics/topics_test.go | 53 ++- 8 files changed, 630 insertions(+), 203 deletions(-) diff --git a/Makefile b/Makefile index 1e7fafb3..90f67fba 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ MVN_VERSION ?= 1.0.0 SHELL := /bin/bash # Coherence CE version to run base tests against -COHERENCE_VERSION ?= 22.06.12 +COHERENCE_VERSION ?= 22.06.13 COHERENCE_GROUP_ID ?= com.oracle.coherence.ce COHERENCE_WKA1 ?= server1 COHERENCE_WKA2 ?= server1 @@ -225,8 +225,8 @@ ifeq ($(SKIP_PROTO_GENERATION),true) @echo "Skipping proto generation..." else mkdir -p $(PROTO_DIR) || true - curl $(CURL_AUTH) -o $(PROTO_DIR)/services.proto https://raw.githubusercontent.com/oracle/coherence/22.06.12/prj/coherence-grpc/src/main/proto/services.proto - curl $(CURL_AUTH) -o $(PROTO_DIR)/messages.proto https://raw.githubusercontent.com/oracle/coherence/22.06.12/prj/coherence-grpc/src/main/proto/messages.proto + curl $(CURL_AUTH) -o $(PROTO_DIR)/services.proto https://raw.githubusercontent.com/oracle/coherence/22.06.13/prj/coherence-grpc/src/main/proto/services.proto + curl $(CURL_AUTH) -o $(PROTO_DIR)/messages.proto https://raw.githubusercontent.com/oracle/coherence/22.06.13/prj/coherence-grpc/src/main/proto/messages.proto echo "" >> $(PROTO_DIR)/services.proto echo "" >> $(PROTO_DIR)/messages.proto echo 'option go_package = "github.com/oracle/coherence-go-client/proto";' >> $(PROTO_DIR)/services.proto diff --git a/coherence/subscriber/subscriber.go b/coherence/subscriber/subscriber.go index 765e768e..855bd569 100644 --- a/coherence/subscriber/subscriber.go +++ b/coherence/subscriber/subscriber.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/oracle/coherence-go-client/v2/coherence/filters" pb1topics "github.com/oracle/coherence-go-client/v2/proto/topics" + "time" ) type ReceiveStatus int32 @@ -29,15 +30,28 @@ type EnsureSubscriberResult struct { UUID string } -type ReceiveResult[V any] struct { - Status ReceiveStatus -} - // Options provides options for creating a subscriber. type Options struct { SubscriberGroup *string Filter filters.Filter Extractor []byte + AutoCommit bool + MaxMessages int32 +} + +// CommitResponse represents th response from a [Subscriber] commit. +type CommitResponse struct { + Channel int32 + Position *pb1topics.TopicPosition + Head *pb1topics.TopicPosition +} + +// ReceiveResponse represents a response from a [Subscriber] receive request. +type ReceiveResponse[V any] struct { + Channel int32 + Value *V + Position *pb1topics.TopicPosition + Timestamp time.Time } // TODO: Additional options @@ -61,6 +75,20 @@ func WithFilter(fltr filters.Filter) func(options *Options) { } } +// WithAutoCommit returns a function to set auto commit to be true for a [Subscriber]. +func WithAutoCommit() func(options *Options) { + return func(s *Options) { + s.AutoCommit = true + } +} + +// WithMaxMessages returns a function to set the maximum messages for a[Subscriber]. +func WithMaxMessages(maxMessages int32) func(options *Options) { + return func(s *Options) { + s.MaxMessages = maxMessages + } +} + // WithTransformer returns a function to set the extractor [Subscriber]. func WithTransformer(extractor []byte) func(options *Options) { return func(s *Options) { diff --git a/coherence/topics.go b/coherence/topics.go index fdc7cacb..087b9dc0 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -36,7 +36,16 @@ var ( ErrTopicsNoSupported = errors.New("the coherence server version must support protocol version 1 or above to use topic") ErrTopicFull = errors.New("unable to publish, this topic is full") ErrPublisherClosed = errors.New("publisher has been closed and is no longer usable") - ErrSubscriberClosed = errors.New("subscriber has been closed and is no longer usable") + + ErrSubscriberClosed = errors.New("subscriber has been closed and is no longer usable") + ErrChannelExhausted = errors.New("channel is exhausted") + ErrChannelNotAllocated = errors.New("channel is not allocated") + ErrUnknownSubscriber = errors.New("unknown subscriber") + + ErrAlreadyCommitted = errors.New("already committed") + ErrCommitRejected = errors.New("commit rejected") + ErrCommitUnowned = errors.New("commit unowned") + ErrNothingToCommit = errors.New("nothing to commit") ) // NamedTopic defines the APIs to interact with Coherence topic allowing to publish and @@ -101,6 +110,13 @@ type Subscriber[V any] interface { // and on the server. Close(ctx context.Context) error + // Receive attempts to receive messages. Messages must be committed to be considered processed unless autoCommit is set. + // If the length of the [subscriber.ReceiveResponse] is zero this means no messages were available. + Receive(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) + + // Commit commits a channel and position meaning the message will be removed from the topic. + Commit(ctx context.Context, channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) + // DestroySubscriberGroup destroys a subscriber group created by this subscriber. DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error @@ -356,10 +372,6 @@ func closeSubscriber[V any](ctx context.Context, ts *topicSubscriber[V]) error { if ts.isClosed { return ErrSubscriberClosed } - err := ts.ensureConnected() - if err != nil { - return err - } ts.isClosed = true ts.session.subscriberIDMap.Remove(ts.SubscriberID) @@ -373,23 +385,79 @@ func closeSubscriber[V any](ctx context.Context, ts *topicSubscriber[V]) error { return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) } +func receiveInternal[V any](ctx context.Context, ts *topicSubscriber[V], maxMessages *int32) ([]*pb1topics.TopicElement, error) { + if ts.isClosed { + return nil, ErrSubscriberClosed + } + + return ts.session.v1StreamManagerTopics.receive(ctx, ts.proxyID, maxMessages) +} + +func commitInternal[V any](ctx context.Context, ts *topicSubscriber[V], channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) { + if ts.isClosed { + return nil, ErrSubscriberClosed + } + + return ts.session.v1StreamManagerTopics.commit(ctx, ts.proxyID, channel, position) +} + func (ts *topicSubscriber[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { return ts.session.v1StreamManagerTopics.destroySubscriberGroup(ctx, ts.proxyID, subscriberGroup) } -func (ts *topicSubscriber[V]) isDisconnected() bool { - ts.mutex.Lock() - defer ts.mutex.Unlock() - return ts.disconnected +// Receive attempts to receive messages. Messages must be committed to be considered processed unless autoCommit is set. +func (ts *topicSubscriber[V]) Receive(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) { + r, err := receiveInternal(ctx, ts, &ts.options.MaxMessages) + if err != nil { + return nil, err + } + + values, err := ts.decodeAll(r) + if err != nil { + return nil, fmt.Errorf("unable to decode receive values: %v", err) + } + + if ts.options.AutoCommit && len(values) > 0 { + // get the last channel and position as this will commit all before + lastChannel := values[len(values)-1].Channel + lastPosition := values[len(values)-1].Position + + _, err = commitInternal(ctx, ts, lastChannel, lastPosition) + if err != nil { + return nil, fmt.Errorf("unable to automatically commit channel %v and position %v", lastChannel, lastPosition) + } + } + + return values, nil } -// ensureConnected ensures the topic is connected. -func (ts *topicSubscriber[V]) ensureConnected() error { - if !ts.isDisconnected() { - return nil +func (ts *topicSubscriber[V]) Commit(ctx context.Context, channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) { + return commitInternal(ctx, ts, channel, position) +} + +func (ts *topicSubscriber[V]) decodeAll(response []*pb1topics.TopicElement) ([]*subscriber.ReceiveResponse[V], error) { + var ( + err error + valueSerializer = ts.valueSerializer + values = make([]*subscriber.ReceiveResponse[V], 0) + value *V + ) + + for _, v := range response { + value, err = valueSerializer.Deserialize(v.Value) + if err != nil { + return nil, err + } + resp := &subscriber.ReceiveResponse[V]{ + Channel: v.Channel, + Value: value, + Position: v.Position, + Timestamp: v.Timestamp.AsTime(), + } + values = append(values, resp) } - return nil + return values, nil } func newBaseTopicsClient[V any](ctx context.Context, session *Session, queueName string, topicID int32, topicOpts *topic.Options) (*baseTopicsClient[V], error) { @@ -574,7 +642,7 @@ func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string return nil, err } - requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscriber) + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSimpleSubscriber) newCtx, cancel := m.session.ensureContext(ctx) if cancel != nil { @@ -604,6 +672,98 @@ func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string return &s, nil } +// receive calls receive for a subscriber. +func (m *streamManagerV1) receive(ctx context.Context, proxyID int32, maxMessages *int32) ([]*pb1topics.TopicElement, error) { + req, err := m.newSubscriberReceiveRequest(proxyID, maxMessages) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_SimpleReceive) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + return decodeReceiveResponse(result) +} + +func decodeReceiveResponse(response *anypb.Any) ([]*pb1topics.TopicElement, error) { + var message = &pb1topics.SimpleReceiveResponse{} + if err := response.UnmarshalTo(message); err != nil { + err = getUnmarshallError("receive subscriber response", err) + return nil, err + } + + if message.Status == pb1topics.ReceiveStatus_ChannelExhausted { + return nil, ErrChannelExhausted + } else if message.Status == pb1topics.ReceiveStatus_ChannelNotAllocatedChannel { + return nil, ErrChannelNotAllocated + } else if message.Status == pb1topics.ReceiveStatus_UnknownSubscriber { + return nil, ErrUnknownSubscriber + } + + return message.Values, nil +} + +// commit calls commit for a subscriber. +func (m *streamManagerV1) commit(ctx context.Context, proxyID int32, channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) { + req, err := m.newSubscriberCommitRequest(proxyID, channel, position) + if err != nil { + return nil, err + } + + requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_CommitPosition) + + newCtx, cancel := m.session.ensureContext(ctx) + if cancel != nil { + defer cancel() + } + + defer m.cleanupRequest(req.Id) + + result, err1 := waitForResponse(newCtx, requestType.ch, false) + if err1 != nil { + return nil, err + } + + return decodeCommitResponse(result) +} + +func decodeCommitResponse(response *anypb.Any) (*subscriber.CommitResponse, error) { + var message = &pb1topics.CommitResponse{} + if err := response.UnmarshalTo(message); err != nil { + err = getUnmarshallError("receive subscriber response", err) + return nil, err + } + + if message.Status == pb1topics.CommitResponseStatus_AlreadyCommitted { + return nil, ErrAlreadyCommitted + } else if message.Status == pb1topics.CommitResponseStatus_Rejected { + return nil, ErrCommitRejected + } else if message.Status == pb1topics.CommitResponseStatus_Unowned { + return nil, ErrCommitUnowned + } else if message.Status == pb1topics.CommitResponseStatus_NothingToCommit { + return nil, ErrNothingToCommit + } + + r := &subscriber.CommitResponse{ + Channel: message.Channel, + Head: message.Head, + Position: message.Position, + } + + return r, nil +} + // ensureSubscriber ensures a subscriber. func (m *streamManagerV1) ensureSubscriberGroup(ctx context.Context, topicName string, subscriberGroup string, binFilter []byte) error { req, err := m.newEnsureSubscriberGroupRequest(topicName, subscriberGroup, binFilter) @@ -678,44 +838,6 @@ func (m *streamManagerV1) destroyPublisherOrSubscriber(ctx context.Context, prox return nil } -// -//// ensureSubscriber ensures a subscriber. -//func (m *streamManagerV1) ensureSubscriptionRequest(ctx context.Context, subscriptionID int64, force bool) (*subscriber.EnsureSubscriberResult, error) { -// req, err := m.newEnsureSubscriptionRequest(subscriptionID, force) -// if err != nil { -// return nil, err -// } -// -// requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_EnsureSubscription) -// -// newCtx, cancel := m.session.ensureContext(ctx) -// if cancel != nil { -// defer cancel() -// } -// -// defer m.cleanupRequest(req.Id) -// -// result, err1 := waitForResponse(newCtx, requestType.ch, false) -// if err1 != nil { -// return nil, err -// } -// -// var message = &pb1topics.EnsureSubscriptionRequest{} -// if err = result.UnmarshalTo(message); err != nil { -// err = getUnmarshallError("ensure subscriber response", err) -// return nil, err -// } -// -// s := subscriber.EnsureSubscriberResult{ProxyID: message.ProxyId} -// -// if message.SubscriberId != nil { -// s.SubscriberID = message.SubscriberId.Id -// s.UUID = string(message.SubscriberId.Uuid) -// } -// -// return &s, nil -//} - // ensureTopic issues the ensure queue request. This must be done before any requests to access queues can be issued. func (m *streamManagerV1) ensureTopic(ctx context.Context, topicName string) (*int32, error) { return m.ensure(ctx, topicName, m.session.topicIDMap, -1, true) @@ -888,7 +1010,7 @@ func (m *streamManagerV1) newDestroyPublisherOrSubscriberRequest(proxyID int32, } func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, subscriberGroup *string, binFilter []byte) (*pb1.ProxyRequest, error) { - req := &pb1topics.EnsureSubscriberRequest{ + req := &pb1topics.EnsureSimpleSubscriberRequest{ Topic: topicName, Filter: binFilter, SubscriberGroup: subscriberGroup, @@ -898,7 +1020,32 @@ func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, subscribe if err != nil { return nil, err } - return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSubscriber, anyReq) + return m.newWrapperProxyTopicsRequest(topicName, pb1topics.TopicServiceRequestType_EnsureSimpleSubscriber, anyReq) +} + +func (m *streamManagerV1) newSubscriberReceiveRequest(ProxyID int32, maxMessages *int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.SimpleReceiveRequest{ + MaxMessages: maxMessages, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyPublisherRequest(ProxyID, pb1topics.TopicServiceRequestType_SimpleReceive, anyReq) +} + +func (m *streamManagerV1) newSubscriberCommitRequest(ProxyID int32, channel int32, position *pb1topics.TopicPosition) (*pb1.ProxyRequest, error) { + req := &pb1topics.ChannelAndPosition{ + Channel: channel, + Position: position, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + return m.newWrapperProxyPublisherRequest(ProxyID, pb1topics.TopicServiceRequestType_CommitPosition, anyReq) } func (m *streamManagerV1) newEnsureSubscriberGroupRequest(topicName string, subscriberGroup string, binFilter []byte) (*pb1.ProxyRequest, error) { @@ -924,23 +1071,8 @@ func (m *streamManagerV1) newDestroySubscriberGroupRequest(proxyID int32, subscr return nil, err } return m.newWrapperProxyPublisherRequest(proxyID, pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) - - //return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_DestroySubscriberGroup, anyReq) } -//func (m *streamManagerV1) newInitializeSubscriptionRequest(subscriptionID int64, force bool) (*pb1.ProxyRequest, error) { -// req := &pb1topics.InitializeSubscriptionRequest{ -// SubscriptionId: subscriptionID, -// ForceReconnect: force, -// } -// -// anyReq, err := anypb.New(req) -// if err != nil { -// return nil, err -// } -// return m.newWrapperProxyTopicsRequest("", pb1topics.TopicServiceRequestType_EnsureSubscription, anyReq) -//} - // genericTopicRequest issues a generic topic request that is further defined by the reqType. func (m *streamManagerV1) genericTopicRequest(ctx context.Context, reqType pb1topics.TopicServiceRequestType, topic string) error { var ( @@ -1003,6 +1135,7 @@ func (m *streamManagerV1) newWrapperProxyTopicsRequest(topicName string, request requestType == pb1topics.TopicServiceRequestType_EnsurePublisher || requestType == pb1topics.TopicServiceRequestType_EnsureSubscription || requestType == pb1topics.TopicServiceRequestType_EnsureSubscriber || + requestType == pb1topics.TopicServiceRequestType_EnsureSimpleSubscriber || requestType == pb1topics.TopicServiceRequestType_DestroySubscriberGroup ) diff --git a/proto/topics/topic_service_messages_v1.pb.go b/proto/topics/topic_service_messages_v1.pb.go index 3962d42b..2de1ac37 100644 --- a/proto/topics/topic_service_messages_v1.pb.go +++ b/proto/topics/topic_service_messages_v1.pb.go @@ -172,6 +172,16 @@ const ( // The message should be a ChannelAndPosition // The response will be a CommitResponse TopicServiceRequestType_CommitPosition TopicServiceRequestType = 25 + // Called to ensure a simple subscriber. + // Must be the first message called prior to any other subscriber requests. + // The message field must be an EnsureSubscriberRequest. + // The response will contain the Subscriber Id and an EnsureSubscriberResponse + // message in the response field. + TopicServiceRequestType_EnsureSimpleSubscriber TopicServiceRequestType = 26 + // Receive values from a simple subscriber. + // The message should be a SimpleReceiveRequest. + // The response will be a ReceiveResponse. + TopicServiceRequestType_SimpleReceive TopicServiceRequestType = 27 ) // Enum value maps for TopicServiceRequestType. @@ -203,6 +213,8 @@ var ( 23: "Receive", 24: "SeekSubscriber", 25: "CommitPosition", + 26: "EnsureSimpleSubscriber", + 27: "SimpleReceive", } TopicServiceRequestType_value = map[string]int32{ "RequestUnknown": 0, @@ -231,6 +243,8 @@ var ( "Receive": 23, "SeekSubscriber": 24, "CommitPosition": 25, + "EnsureSimpleSubscriber": 26, + "SimpleReceive": 27, } ) @@ -1789,6 +1803,97 @@ func (x *EnsureSubscriberRequest) GetChannels() []int32 { return nil } +// A request to ensure a specific simple subscriber. +type EnsureSimpleSubscriberRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The name of the topic. + Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"` + // the optional name of the subscriber group + SubscriberGroup *string `protobuf:"bytes,2,opt,name=subscriberGroup,proto3,oneof" json:"subscriberGroup,omitempty"` + // an optional Filter to filter received messages + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + // an optional ValueExtractor to convert received messages + Extractor []byte `protobuf:"bytes,4,opt,name=extractor,proto3,oneof" json:"extractor,omitempty"` + // True to return an empty value if the topic is empty + CompleteOnEmpty bool `protobuf:"varint,5,opt,name=completeOnEmpty,proto3" json:"completeOnEmpty,omitempty"` + // The channels to allocate to this subscriber (invalid channels will be ignored) + Channels []int32 `protobuf:"varint,6,rep,packed,name=channels,proto3" json:"channels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureSimpleSubscriberRequest) Reset() { + *x = EnsureSimpleSubscriberRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureSimpleSubscriberRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureSimpleSubscriberRequest) ProtoMessage() {} + +func (x *EnsureSimpleSubscriberRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureSimpleSubscriberRequest.ProtoReflect.Descriptor instead. +func (*EnsureSimpleSubscriberRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{17} +} + +func (x *EnsureSimpleSubscriberRequest) GetTopic() string { + if x != nil { + return x.Topic + } + return "" +} + +func (x *EnsureSimpleSubscriberRequest) GetSubscriberGroup() string { + if x != nil && x.SubscriberGroup != nil { + return *x.SubscriberGroup + } + return "" +} + +func (x *EnsureSimpleSubscriberRequest) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +func (x *EnsureSimpleSubscriberRequest) GetExtractor() []byte { + if x != nil { + return x.Extractor + } + return nil +} + +func (x *EnsureSimpleSubscriberRequest) GetCompleteOnEmpty() bool { + if x != nil { + return x.CompleteOnEmpty + } + return false +} + +func (x *EnsureSimpleSubscriberRequest) GetChannels() []int32 { + if x != nil { + return x.Channels + } + return nil +} + // An event to indicate the state of a NamedTopic Subscriber has changed. type SubscriberEvent struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1802,7 +1907,7 @@ type SubscriberEvent struct { func (x *SubscriberEvent) Reset() { *x = SubscriberEvent{} - mi := &file_topic_service_messages_v1_proto_msgTypes[17] + mi := &file_topic_service_messages_v1_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1814,7 +1919,7 @@ func (x *SubscriberEvent) String() string { func (*SubscriberEvent) ProtoMessage() {} func (x *SubscriberEvent) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[17] + mi := &file_topic_service_messages_v1_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1827,7 +1932,7 @@ func (x *SubscriberEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriberEvent.ProtoReflect.Descriptor instead. func (*SubscriberEvent) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{17} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{18} } func (x *SubscriberEvent) GetType() SubscriberEventType { @@ -1857,7 +1962,7 @@ type SubscriberId struct { func (x *SubscriberId) Reset() { *x = SubscriberId{} - mi := &file_topic_service_messages_v1_proto_msgTypes[18] + mi := &file_topic_service_messages_v1_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1869,7 +1974,7 @@ func (x *SubscriberId) String() string { func (*SubscriberId) ProtoMessage() {} func (x *SubscriberId) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[18] + mi := &file_topic_service_messages_v1_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1882,7 +1987,7 @@ func (x *SubscriberId) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriberId.ProtoReflect.Descriptor instead. func (*SubscriberId) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{18} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{19} } func (x *SubscriberId) GetId() int64 { @@ -1912,7 +2017,7 @@ type SubscriberGroupId struct { func (x *SubscriberGroupId) Reset() { *x = SubscriberGroupId{} - mi := &file_topic_service_messages_v1_proto_msgTypes[19] + mi := &file_topic_service_messages_v1_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1924,7 +2029,7 @@ func (x *SubscriberGroupId) String() string { func (*SubscriberGroupId) ProtoMessage() {} func (x *SubscriberGroupId) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[19] + mi := &file_topic_service_messages_v1_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1937,7 +2042,7 @@ func (x *SubscriberGroupId) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriberGroupId.ProtoReflect.Descriptor instead. func (*SubscriberGroupId) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{19} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{20} } func (x *SubscriberGroupId) GetName() string { @@ -1970,7 +2075,7 @@ type InitializeSubscriptionRequest struct { func (x *InitializeSubscriptionRequest) Reset() { *x = InitializeSubscriptionRequest{} - mi := &file_topic_service_messages_v1_proto_msgTypes[20] + mi := &file_topic_service_messages_v1_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1982,7 +2087,7 @@ func (x *InitializeSubscriptionRequest) String() string { func (*InitializeSubscriptionRequest) ProtoMessage() {} func (x *InitializeSubscriptionRequest) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[20] + mi := &file_topic_service_messages_v1_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1995,7 +2100,7 @@ func (x *InitializeSubscriptionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InitializeSubscriptionRequest.ProtoReflect.Descriptor instead. func (*InitializeSubscriptionRequest) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{20} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{21} } func (x *InitializeSubscriptionRequest) GetDisconnected() bool { @@ -2034,7 +2139,7 @@ type InitializeSubscriptionResponse struct { func (x *InitializeSubscriptionResponse) Reset() { *x = InitializeSubscriptionResponse{} - mi := &file_topic_service_messages_v1_proto_msgTypes[21] + mi := &file_topic_service_messages_v1_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2046,7 +2151,7 @@ func (x *InitializeSubscriptionResponse) String() string { func (*InitializeSubscriptionResponse) ProtoMessage() {} func (x *InitializeSubscriptionResponse) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[21] + mi := &file_topic_service_messages_v1_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2059,7 +2164,7 @@ func (x *InitializeSubscriptionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InitializeSubscriptionResponse.ProtoReflect.Descriptor instead. func (*InitializeSubscriptionResponse) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{21} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{22} } func (x *InitializeSubscriptionResponse) GetSubscriptionId() int64 { @@ -2097,7 +2202,7 @@ type EnsureSubscriptionRequest struct { func (x *EnsureSubscriptionRequest) Reset() { *x = EnsureSubscriptionRequest{} - mi := &file_topic_service_messages_v1_proto_msgTypes[22] + mi := &file_topic_service_messages_v1_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2109,7 +2214,7 @@ func (x *EnsureSubscriptionRequest) String() string { func (*EnsureSubscriptionRequest) ProtoMessage() {} func (x *EnsureSubscriptionRequest) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[22] + mi := &file_topic_service_messages_v1_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2122,7 +2227,7 @@ func (x *EnsureSubscriptionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EnsureSubscriptionRequest.ProtoReflect.Descriptor instead. func (*EnsureSubscriptionRequest) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{22} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{23} } func (x *EnsureSubscriptionRequest) GetSubscriptionId() int64 { @@ -2156,7 +2261,7 @@ type TopicElement struct { func (x *TopicElement) Reset() { *x = TopicElement{} - mi := &file_topic_service_messages_v1_proto_msgTypes[23] + mi := &file_topic_service_messages_v1_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2168,7 +2273,7 @@ func (x *TopicElement) String() string { func (*TopicElement) ProtoMessage() {} func (x *TopicElement) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[23] + mi := &file_topic_service_messages_v1_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2181,7 +2286,7 @@ func (x *TopicElement) ProtoReflect() protoreflect.Message { // Deprecated: Use TopicElement.ProtoReflect.Descriptor instead. func (*TopicElement) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{23} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{24} } func (x *TopicElement) GetChannel() int32 { @@ -2227,7 +2332,7 @@ type ReceiveRequest struct { func (x *ReceiveRequest) Reset() { *x = ReceiveRequest{} - mi := &file_topic_service_messages_v1_proto_msgTypes[24] + mi := &file_topic_service_messages_v1_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2239,7 +2344,7 @@ func (x *ReceiveRequest) String() string { func (*ReceiveRequest) ProtoMessage() {} func (x *ReceiveRequest) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[24] + mi := &file_topic_service_messages_v1_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2252,7 +2357,7 @@ func (x *ReceiveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReceiveRequest.ProtoReflect.Descriptor instead. func (*ReceiveRequest) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{24} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{25} } func (x *ReceiveRequest) GetChannel() int32 { @@ -2269,6 +2374,53 @@ func (x *ReceiveRequest) GetMaxMessages() int32 { return 0 } +type SimpleReceiveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The maximum number of messages to return. + // If not set (or <= 0) a single messages will be returned. + MaxMessages *int32 `protobuf:"varint,2,opt,name=maxMessages,proto3,oneof" json:"maxMessages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SimpleReceiveRequest) Reset() { + *x = SimpleReceiveRequest{} + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SimpleReceiveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SimpleReceiveRequest) ProtoMessage() {} + +func (x *SimpleReceiveRequest) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SimpleReceiveRequest.ProtoReflect.Descriptor instead. +func (*SimpleReceiveRequest) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{26} +} + +func (x *SimpleReceiveRequest) GetMaxMessages() int32 { + if x != nil && x.MaxMessages != nil { + return *x.MaxMessages + } + return 0 +} + +// A response from a ReceiveRequest type ReceiveResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // The status of the receive result. @@ -2285,7 +2437,7 @@ type ReceiveResponse struct { func (x *ReceiveResponse) Reset() { *x = ReceiveResponse{} - mi := &file_topic_service_messages_v1_proto_msgTypes[25] + mi := &file_topic_service_messages_v1_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2297,7 +2449,7 @@ func (x *ReceiveResponse) String() string { func (*ReceiveResponse) ProtoMessage() {} func (x *ReceiveResponse) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[25] + mi := &file_topic_service_messages_v1_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2310,7 +2462,7 @@ func (x *ReceiveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReceiveResponse.ProtoReflect.Descriptor instead. func (*ReceiveResponse) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{25} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{27} } func (x *ReceiveResponse) GetStatus() ReceiveStatus { @@ -2341,6 +2493,61 @@ func (x *ReceiveResponse) GetRemainingValues() int32 { return 0 } +// A response from a SimpleReceiveRequest +type SimpleReceiveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The status of the receive result. + Status ReceiveStatus `protobuf:"varint,1,opt,name=status,proto3,enum=coherence.topic.v1.ReceiveStatus" json:"status,omitempty"` + // The elements received from the channel. + Values []*TopicElement `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SimpleReceiveResponse) Reset() { + *x = SimpleReceiveResponse{} + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SimpleReceiveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SimpleReceiveResponse) ProtoMessage() {} + +func (x *SimpleReceiveResponse) ProtoReflect() protoreflect.Message { + mi := &file_topic_service_messages_v1_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SimpleReceiveResponse.ProtoReflect.Descriptor instead. +func (*SimpleReceiveResponse) Descriptor() ([]byte, []int) { + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{28} +} + +func (x *SimpleReceiveResponse) GetStatus() ReceiveStatus { + if x != nil { + return x.Status + } + return ReceiveStatus_ReceiveSuccess +} + +func (x *SimpleReceiveResponse) GetValues() []*TopicElement { + if x != nil { + return x.Values + } + return nil +} + // A request to seek (reposition) one or more channels. type SeekRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2357,7 +2564,7 @@ type SeekRequest struct { func (x *SeekRequest) Reset() { *x = SeekRequest{} - mi := &file_topic_service_messages_v1_proto_msgTypes[26] + mi := &file_topic_service_messages_v1_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2369,7 +2576,7 @@ func (x *SeekRequest) String() string { func (*SeekRequest) ProtoMessage() {} func (x *SeekRequest) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[26] + mi := &file_topic_service_messages_v1_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2382,7 +2589,7 @@ func (x *SeekRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SeekRequest.ProtoReflect.Descriptor instead. func (*SeekRequest) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{26} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{29} } func (x *SeekRequest) GetPositions() isSeekRequest_Positions { @@ -2441,7 +2648,7 @@ type SeekedPositions struct { func (x *SeekedPositions) Reset() { *x = SeekedPositions{} - mi := &file_topic_service_messages_v1_proto_msgTypes[27] + mi := &file_topic_service_messages_v1_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2453,7 +2660,7 @@ func (x *SeekedPositions) String() string { func (*SeekedPositions) ProtoMessage() {} func (x *SeekedPositions) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[27] + mi := &file_topic_service_messages_v1_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2466,7 +2673,7 @@ func (x *SeekedPositions) ProtoReflect() protoreflect.Message { // Deprecated: Use SeekedPositions.ProtoReflect.Descriptor instead. func (*SeekedPositions) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{27} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{30} } func (x *SeekedPositions) GetHead() *TopicPosition { @@ -2494,7 +2701,7 @@ type SeekResponse struct { func (x *SeekResponse) Reset() { *x = SeekResponse{} - mi := &file_topic_service_messages_v1_proto_msgTypes[28] + mi := &file_topic_service_messages_v1_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2506,7 +2713,7 @@ func (x *SeekResponse) String() string { func (*SeekResponse) ProtoMessage() {} func (x *SeekResponse) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[28] + mi := &file_topic_service_messages_v1_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2519,7 +2726,7 @@ func (x *SeekResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SeekResponse.ProtoReflect.Descriptor instead. func (*SeekResponse) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{28} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{31} } func (x *SeekResponse) GetPositions() map[int32]*SeekedPositions { @@ -2548,7 +2755,7 @@ type CommitResponse struct { func (x *CommitResponse) Reset() { *x = CommitResponse{} - mi := &file_topic_service_messages_v1_proto_msgTypes[29] + mi := &file_topic_service_messages_v1_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2560,7 +2767,7 @@ func (x *CommitResponse) String() string { func (*CommitResponse) ProtoMessage() {} func (x *CommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[29] + mi := &file_topic_service_messages_v1_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2573,7 +2780,7 @@ func (x *CommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. func (*CommitResponse) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{29} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{32} } func (x *CommitResponse) GetChannel() int32 { @@ -2622,7 +2829,7 @@ type ChannelAndPosition struct { func (x *ChannelAndPosition) Reset() { *x = ChannelAndPosition{} - mi := &file_topic_service_messages_v1_proto_msgTypes[30] + mi := &file_topic_service_messages_v1_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2634,7 +2841,7 @@ func (x *ChannelAndPosition) String() string { func (*ChannelAndPosition) ProtoMessage() {} func (x *ChannelAndPosition) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[30] + mi := &file_topic_service_messages_v1_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2647,7 +2854,7 @@ func (x *ChannelAndPosition) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelAndPosition.ProtoReflect.Descriptor instead. func (*ChannelAndPosition) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{30} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{33} } func (x *ChannelAndPosition) GetChannel() int32 { @@ -2675,7 +2882,7 @@ type MapOfChannelAndPosition struct { func (x *MapOfChannelAndPosition) Reset() { *x = MapOfChannelAndPosition{} - mi := &file_topic_service_messages_v1_proto_msgTypes[31] + mi := &file_topic_service_messages_v1_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2687,7 +2894,7 @@ func (x *MapOfChannelAndPosition) String() string { func (*MapOfChannelAndPosition) ProtoMessage() {} func (x *MapOfChannelAndPosition) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[31] + mi := &file_topic_service_messages_v1_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2700,7 +2907,7 @@ func (x *MapOfChannelAndPosition) ProtoReflect() protoreflect.Message { // Deprecated: Use MapOfChannelAndPosition.ProtoReflect.Descriptor instead. func (*MapOfChannelAndPosition) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{31} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{34} } func (x *MapOfChannelAndPosition) GetPositions() map[int32]*TopicPosition { @@ -2721,7 +2928,7 @@ type MapOfChannelAndTimestamp struct { func (x *MapOfChannelAndTimestamp) Reset() { *x = MapOfChannelAndTimestamp{} - mi := &file_topic_service_messages_v1_proto_msgTypes[32] + mi := &file_topic_service_messages_v1_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2733,7 +2940,7 @@ func (x *MapOfChannelAndTimestamp) String() string { func (*MapOfChannelAndTimestamp) ProtoMessage() {} func (x *MapOfChannelAndTimestamp) ProtoReflect() protoreflect.Message { - mi := &file_topic_service_messages_v1_proto_msgTypes[32] + mi := &file_topic_service_messages_v1_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2746,7 +2953,7 @@ func (x *MapOfChannelAndTimestamp) ProtoReflect() protoreflect.Message { // Deprecated: Use MapOfChannelAndTimestamp.ProtoReflect.Descriptor instead. func (*MapOfChannelAndTimestamp) Descriptor() ([]byte, []int) { - return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{32} + return file_topic_service_messages_v1_proto_rawDescGZIP(), []int{35} } func (x *MapOfChannelAndTimestamp) GetTimestamps() map[int32]*timestamppb.Timestamp { @@ -2842,6 +3049,17 @@ const file_topic_service_messages_v1_proto_rawDesc = "" + "\x10_subscriberGroupB\t\n" + "\a_filterB\f\n" + "\n" + + "_extractor\"\x97\x02\n" + + "\x1dEnsureSimpleSubscriberRequest\x12\x14\n" + + "\x05topic\x18\x01 \x01(\tR\x05topic\x12-\n" + + "\x0fsubscriberGroup\x18\x02 \x01(\tH\x00R\x0fsubscriberGroup\x88\x01\x01\x12\x1b\n" + + "\x06filter\x18\x03 \x01(\fH\x01R\x06filter\x88\x01\x01\x12!\n" + + "\textractor\x18\x04 \x01(\fH\x02R\textractor\x88\x01\x01\x12(\n" + + "\x0fcompleteOnEmpty\x18\x05 \x01(\bR\x0fcompleteOnEmpty\x12\x1a\n" + + "\bchannels\x18\x06 \x03(\x05R\bchannelsB\x12\n" + + "\x10_subscriberGroupB\t\n" + + "\a_filterB\f\n" + + "\n" + "_extractor\"n\n" + "\x0fSubscriberEvent\x12;\n" + "\x04type\x18\x01 \x01(\x0e2'.coherence.topic.v1.SubscriberEventTypeR\x04type\x12\x1e\n" + @@ -2871,12 +3089,18 @@ const file_topic_service_messages_v1_proto_rawDesc = "" + "\x0eReceiveRequest\x12\x18\n" + "\achannel\x18\x01 \x01(\x05R\achannel\x12%\n" + "\vmaxMessages\x18\x02 \x01(\x05H\x00R\vmaxMessages\x88\x01\x01B\x0e\n" + + "\f_maxMessages\"M\n" + + "\x14SimpleReceiveRequest\x12%\n" + + "\vmaxMessages\x18\x02 \x01(\x05H\x00R\vmaxMessages\x88\x01\x01B\x0e\n" + "\f_maxMessages\"\xd5\x01\n" + "\x0fReceiveResponse\x129\n" + "\x06status\x18\x01 \x01(\x0e2!.coherence.topic.v1.ReceiveStatusR\x06status\x12\x16\n" + "\x06values\x18\x02 \x03(\fR\x06values\x12E\n" + "\fheadPosition\x18\x03 \x01(\v2!.coherence.topic.v1.TopicPositionR\fheadPosition\x12(\n" + - "\x0fremainingValues\x18\x04 \x01(\x05R\x0fremainingValues\"\xbb\x01\n" + + "\x0fremainingValues\x18\x04 \x01(\x05R\x0fremainingValues\"\x8c\x01\n" + + "\x15SimpleReceiveResponse\x129\n" + + "\x06status\x18\x01 \x01(\x0e2!.coherence.topic.v1.ReceiveStatusR\x06status\x128\n" + + "\x06values\x18\x02 \x03(\v2 .coherence.topic.v1.TopicElementR\x06values\"\xbb\x01\n" + "\vSeekRequest\x12M\n" + "\n" + "byPosition\x18\x01 \x01(\v2+.coherence.topic.v1.MapOfChannelAndPositionH\x00R\n" + @@ -2912,7 +3136,7 @@ const file_topic_service_messages_v1_proto_rawDesc = "" + "timestamps\x1aY\n" + "\x0fTimestampsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\x05R\x03key\x120\n" + - "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01*\xcc\x04\n" + + "\x05value\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05value:\x028\x01*\xfb\x04\n" + "\x17TopicServiceRequestType\x12\x12\n" + "\x0eRequestUnknown\x10\x00\x12\x0f\n" + "\vEnsureTopic\x10\x01\x12\x10\n" + @@ -2940,7 +3164,9 @@ const file_topic_service_messages_v1_proto_rawDesc = "" + "\x0ePeekAtPosition\x10\x16\x12\v\n" + "\aReceive\x10\x17\x12\x12\n" + "\x0eSeekSubscriber\x10\x18\x12\x12\n" + - "\x0eCommitPosition\x10\x19*&\n" + + "\x0eCommitPosition\x10\x19\x12\x1a\n" + + "\x16EnsureSimpleSubscriber\x10\x1a\x12\x11\n" + + "\rSimpleReceive\x10\x1b*&\n" + "\fResponseType\x12\v\n" + "\aMessage\x10\x00\x12\t\n" + "\x05Event\x10\x01*6\n" + @@ -2994,7 +3220,7 @@ func file_topic_service_messages_v1_proto_rawDescGZIP() []byte { } var file_topic_service_messages_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 8) -var file_topic_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_topic_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 39) var file_topic_service_messages_v1_proto_goTypes = []any{ (TopicServiceRequestType)(0), // 0: coherence.topic.v1.TopicServiceRequestType (ResponseType)(0), // 1: coherence.topic.v1.ResponseType @@ -3021,70 +3247,75 @@ var file_topic_service_messages_v1_proto_goTypes = []any{ (*TopicPosition)(nil), // 22: coherence.topic.v1.TopicPosition (*EnsureSubscriberResponse)(nil), // 23: coherence.topic.v1.EnsureSubscriberResponse (*EnsureSubscriberRequest)(nil), // 24: coherence.topic.v1.EnsureSubscriberRequest - (*SubscriberEvent)(nil), // 25: coherence.topic.v1.SubscriberEvent - (*SubscriberId)(nil), // 26: coherence.topic.v1.SubscriberId - (*SubscriberGroupId)(nil), // 27: coherence.topic.v1.SubscriberGroupId - (*InitializeSubscriptionRequest)(nil), // 28: coherence.topic.v1.InitializeSubscriptionRequest - (*InitializeSubscriptionResponse)(nil), // 29: coherence.topic.v1.InitializeSubscriptionResponse - (*EnsureSubscriptionRequest)(nil), // 30: coherence.topic.v1.EnsureSubscriptionRequest - (*TopicElement)(nil), // 31: coherence.topic.v1.TopicElement - (*ReceiveRequest)(nil), // 32: coherence.topic.v1.ReceiveRequest - (*ReceiveResponse)(nil), // 33: coherence.topic.v1.ReceiveResponse - (*SeekRequest)(nil), // 34: coherence.topic.v1.SeekRequest - (*SeekedPositions)(nil), // 35: coherence.topic.v1.SeekedPositions - (*SeekResponse)(nil), // 36: coherence.topic.v1.SeekResponse - (*CommitResponse)(nil), // 37: coherence.topic.v1.CommitResponse - (*ChannelAndPosition)(nil), // 38: coherence.topic.v1.ChannelAndPosition - (*MapOfChannelAndPosition)(nil), // 39: coherence.topic.v1.MapOfChannelAndPosition - (*MapOfChannelAndTimestamp)(nil), // 40: coherence.topic.v1.MapOfChannelAndTimestamp - nil, // 41: coherence.topic.v1.SeekResponse.PositionsEntry - nil, // 42: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry - nil, // 43: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry - (*anypb.Any)(nil), // 44: google.protobuf.Any - (*v1.ErrorMessage)(nil), // 45: coherence.common.v1.ErrorMessage - (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp + (*EnsureSimpleSubscriberRequest)(nil), // 25: coherence.topic.v1.EnsureSimpleSubscriberRequest + (*SubscriberEvent)(nil), // 26: coherence.topic.v1.SubscriberEvent + (*SubscriberId)(nil), // 27: coherence.topic.v1.SubscriberId + (*SubscriberGroupId)(nil), // 28: coherence.topic.v1.SubscriberGroupId + (*InitializeSubscriptionRequest)(nil), // 29: coherence.topic.v1.InitializeSubscriptionRequest + (*InitializeSubscriptionResponse)(nil), // 30: coherence.topic.v1.InitializeSubscriptionResponse + (*EnsureSubscriptionRequest)(nil), // 31: coherence.topic.v1.EnsureSubscriptionRequest + (*TopicElement)(nil), // 32: coherence.topic.v1.TopicElement + (*ReceiveRequest)(nil), // 33: coherence.topic.v1.ReceiveRequest + (*SimpleReceiveRequest)(nil), // 34: coherence.topic.v1.SimpleReceiveRequest + (*ReceiveResponse)(nil), // 35: coherence.topic.v1.ReceiveResponse + (*SimpleReceiveResponse)(nil), // 36: coherence.topic.v1.SimpleReceiveResponse + (*SeekRequest)(nil), // 37: coherence.topic.v1.SeekRequest + (*SeekedPositions)(nil), // 38: coherence.topic.v1.SeekedPositions + (*SeekResponse)(nil), // 39: coherence.topic.v1.SeekResponse + (*CommitResponse)(nil), // 40: coherence.topic.v1.CommitResponse + (*ChannelAndPosition)(nil), // 41: coherence.topic.v1.ChannelAndPosition + (*MapOfChannelAndPosition)(nil), // 42: coherence.topic.v1.MapOfChannelAndPosition + (*MapOfChannelAndTimestamp)(nil), // 43: coherence.topic.v1.MapOfChannelAndTimestamp + nil, // 44: coherence.topic.v1.SeekResponse.PositionsEntry + nil, // 45: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + nil, // 46: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + (*anypb.Any)(nil), // 47: google.protobuf.Any + (*v1.ErrorMessage)(nil), // 48: coherence.common.v1.ErrorMessage + (*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp } var file_topic_service_messages_v1_proto_depIdxs = []int32{ 0, // 0: coherence.topic.v1.TopicServiceRequest.type:type_name -> coherence.topic.v1.TopicServiceRequestType - 44, // 1: coherence.topic.v1.TopicServiceRequest.message:type_name -> google.protobuf.Any + 47, // 1: coherence.topic.v1.TopicServiceRequest.message:type_name -> google.protobuf.Any 1, // 2: coherence.topic.v1.TopicServiceResponse.type:type_name -> coherence.topic.v1.ResponseType - 44, // 3: coherence.topic.v1.TopicServiceResponse.message:type_name -> google.protobuf.Any + 47, // 3: coherence.topic.v1.TopicServiceResponse.message:type_name -> google.protobuf.Any 2, // 4: coherence.topic.v1.NamedTopicEvent.type:type_name -> coherence.topic.v1.TopicEventType 3, // 5: coherence.topic.v1.PublisherEvent.type:type_name -> coherence.topic.v1.PublisherEventType 22, // 6: coherence.topic.v1.PublishedValueStatus.position:type_name -> coherence.topic.v1.TopicPosition - 45, // 7: coherence.topic.v1.PublishedValueStatus.error:type_name -> coherence.common.v1.ErrorMessage + 48, // 7: coherence.topic.v1.PublishedValueStatus.error:type_name -> coherence.common.v1.ErrorMessage 4, // 8: coherence.topic.v1.PublishResult.status:type_name -> coherence.topic.v1.PublishStatus 19, // 9: coherence.topic.v1.PublishResult.valueStatus:type_name -> coherence.topic.v1.PublishedValueStatus - 44, // 10: coherence.topic.v1.TopicPosition.position:type_name -> google.protobuf.Any - 26, // 11: coherence.topic.v1.EnsureSubscriberResponse.subscriberId:type_name -> coherence.topic.v1.SubscriberId - 27, // 12: coherence.topic.v1.EnsureSubscriberResponse.groupId:type_name -> coherence.topic.v1.SubscriberGroupId + 47, // 10: coherence.topic.v1.TopicPosition.position:type_name -> google.protobuf.Any + 27, // 11: coherence.topic.v1.EnsureSubscriberResponse.subscriberId:type_name -> coherence.topic.v1.SubscriberId + 28, // 12: coherence.topic.v1.EnsureSubscriberResponse.groupId:type_name -> coherence.topic.v1.SubscriberGroupId 5, // 13: coherence.topic.v1.SubscriberEvent.type:type_name -> coherence.topic.v1.SubscriberEventType - 46, // 14: coherence.topic.v1.InitializeSubscriptionResponse.timestamp:type_name -> google.protobuf.Timestamp + 49, // 14: coherence.topic.v1.InitializeSubscriptionResponse.timestamp:type_name -> google.protobuf.Timestamp 22, // 15: coherence.topic.v1.InitializeSubscriptionResponse.heads:type_name -> coherence.topic.v1.TopicPosition 22, // 16: coherence.topic.v1.TopicElement.position:type_name -> coherence.topic.v1.TopicPosition - 46, // 17: coherence.topic.v1.TopicElement.timestamp:type_name -> google.protobuf.Timestamp + 49, // 17: coherence.topic.v1.TopicElement.timestamp:type_name -> google.protobuf.Timestamp 6, // 18: coherence.topic.v1.ReceiveResponse.status:type_name -> coherence.topic.v1.ReceiveStatus 22, // 19: coherence.topic.v1.ReceiveResponse.headPosition:type_name -> coherence.topic.v1.TopicPosition - 39, // 20: coherence.topic.v1.SeekRequest.byPosition:type_name -> coherence.topic.v1.MapOfChannelAndPosition - 40, // 21: coherence.topic.v1.SeekRequest.byTimestamp:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp - 22, // 22: coherence.topic.v1.SeekedPositions.head:type_name -> coherence.topic.v1.TopicPosition - 22, // 23: coherence.topic.v1.SeekedPositions.seekedTo:type_name -> coherence.topic.v1.TopicPosition - 41, // 24: coherence.topic.v1.SeekResponse.positions:type_name -> coherence.topic.v1.SeekResponse.PositionsEntry - 22, // 25: coherence.topic.v1.CommitResponse.position:type_name -> coherence.topic.v1.TopicPosition - 22, // 26: coherence.topic.v1.CommitResponse.head:type_name -> coherence.topic.v1.TopicPosition - 7, // 27: coherence.topic.v1.CommitResponse.status:type_name -> coherence.topic.v1.CommitResponseStatus - 45, // 28: coherence.topic.v1.CommitResponse.error:type_name -> coherence.common.v1.ErrorMessage - 22, // 29: coherence.topic.v1.ChannelAndPosition.position:type_name -> coherence.topic.v1.TopicPosition - 42, // 30: coherence.topic.v1.MapOfChannelAndPosition.positions:type_name -> coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry - 43, // 31: coherence.topic.v1.MapOfChannelAndTimestamp.timestamps:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry - 35, // 32: coherence.topic.v1.SeekResponse.PositionsEntry.value:type_name -> coherence.topic.v1.SeekedPositions - 22, // 33: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry.value:type_name -> coherence.topic.v1.TopicPosition - 46, // 34: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry.value:type_name -> google.protobuf.Timestamp - 35, // [35:35] is the sub-list for method output_type - 35, // [35:35] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 6, // 20: coherence.topic.v1.SimpleReceiveResponse.status:type_name -> coherence.topic.v1.ReceiveStatus + 32, // 21: coherence.topic.v1.SimpleReceiveResponse.values:type_name -> coherence.topic.v1.TopicElement + 42, // 22: coherence.topic.v1.SeekRequest.byPosition:type_name -> coherence.topic.v1.MapOfChannelAndPosition + 43, // 23: coherence.topic.v1.SeekRequest.byTimestamp:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp + 22, // 24: coherence.topic.v1.SeekedPositions.head:type_name -> coherence.topic.v1.TopicPosition + 22, // 25: coherence.topic.v1.SeekedPositions.seekedTo:type_name -> coherence.topic.v1.TopicPosition + 44, // 26: coherence.topic.v1.SeekResponse.positions:type_name -> coherence.topic.v1.SeekResponse.PositionsEntry + 22, // 27: coherence.topic.v1.CommitResponse.position:type_name -> coherence.topic.v1.TopicPosition + 22, // 28: coherence.topic.v1.CommitResponse.head:type_name -> coherence.topic.v1.TopicPosition + 7, // 29: coherence.topic.v1.CommitResponse.status:type_name -> coherence.topic.v1.CommitResponseStatus + 48, // 30: coherence.topic.v1.CommitResponse.error:type_name -> coherence.common.v1.ErrorMessage + 22, // 31: coherence.topic.v1.ChannelAndPosition.position:type_name -> coherence.topic.v1.TopicPosition + 45, // 32: coherence.topic.v1.MapOfChannelAndPosition.positions:type_name -> coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry + 46, // 33: coherence.topic.v1.MapOfChannelAndTimestamp.timestamps:type_name -> coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry + 38, // 34: coherence.topic.v1.SeekResponse.PositionsEntry.value:type_name -> coherence.topic.v1.SeekedPositions + 22, // 35: coherence.topic.v1.MapOfChannelAndPosition.PositionsEntry.value:type_name -> coherence.topic.v1.TopicPosition + 49, // 36: coherence.topic.v1.MapOfChannelAndTimestamp.TimestampsEntry.value:type_name -> google.protobuf.Timestamp + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_topic_service_messages_v1_proto_init() } @@ -3103,19 +3334,21 @@ func file_topic_service_messages_v1_proto_init() { (*PublishedValueStatus_Error)(nil), } file_topic_service_messages_v1_proto_msgTypes[16].OneofWrappers = []any{} - file_topic_service_messages_v1_proto_msgTypes[24].OneofWrappers = []any{} - file_topic_service_messages_v1_proto_msgTypes[26].OneofWrappers = []any{ + file_topic_service_messages_v1_proto_msgTypes[17].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[25].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[26].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[29].OneofWrappers = []any{ (*SeekRequest_ByPosition)(nil), (*SeekRequest_ByTimestamp)(nil), } - file_topic_service_messages_v1_proto_msgTypes[29].OneofWrappers = []any{} + file_topic_service_messages_v1_proto_msgTypes[32].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_topic_service_messages_v1_proto_rawDesc), len(file_topic_service_messages_v1_proto_rawDesc)), NumEnums: 8, - NumMessages: 36, + NumMessages: 39, NumExtensions: 0, NumServices: 0, }, diff --git a/test/e2e/standalone/event_test.go b/test/e2e/standalone/event_test.go index 20e0cc66..a3b3f2e0 100644 --- a/test/e2e/standalone/event_test.go +++ b/test/e2e/standalone/event_test.go @@ -899,7 +899,6 @@ func runMultipleLifecycleTests(g *gomega.WithT, cache coherence.NamedMap[string, log.Println("Truncate - 2", cache.Name()) err = cache.Truncate(ctx) g.Expect(err).ShouldNot(gomega.HaveOccurred()) - //time.Sleep(time.Duration(5) * time.Second) // each of the listeners should receive 2 events g.Eventually(func() int32 { diff --git a/test/e2e/topics/subscriber_event_test.go b/test/e2e/topics/subscriber_event_test.go index b26493d6..f47a27e7 100644 --- a/test/e2e/topics/subscriber_event_test.go +++ b/test/e2e/topics/subscriber_event_test.go @@ -23,6 +23,8 @@ func TestSubscriberEventsDestroyOnServer(t *testing.T) { err error ) + t.Skip("temporarily disable") + const ( topicName = "subscriber-events" subscriberGroupName = "subscriber-events" diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index 84f19ffc..9cc72f95 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -21,9 +21,10 @@ import ( func TestSubscriberWithFilter(t *testing.T) { var ( - g = gomega.NewWithT(t) - err error - ctx = context.Background() + g = gomega.NewWithT(t) + err error + ctx = context.Background() + maxMessages = 1000 ) const topicName = "my-topic-anon-filter" @@ -36,7 +37,11 @@ func TestSubscriberWithFilter(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - runTestPerson(g, topic1, sub1) + go func() { + + }() + + runTestPerson(g, topic1, sub1, maxMessages) } func TestSubscriberWithTransformer(t *testing.T) { @@ -52,13 +57,13 @@ func TestSubscriberWithTransformer(t *testing.T) { defer session1.Close() extractor := extractors.Extract[string]("name") - // create a subscriber with a transformer, this + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - runTestPerson(g, topic1, sub1) + runTestPerson(g, topic1, sub1, 1_000) } func TestSubscriberWithTransformerAndFilter(t *testing.T) { diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index 5a4a7391..46dbe02a 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -12,6 +12,7 @@ import ( "github.com/onsi/gomega" "github.com/oracle/coherence-go-client/v2/coherence" "github.com/oracle/coherence-go-client/v2/coherence/publisher" + "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/coherence/topic" "github.com/oracle/coherence-go-client/v2/test/utils" "log" @@ -54,8 +55,11 @@ func TestTopicPublish(t *testing.T) { func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publisher.Options)) { var ( - err error - ctx = context.Background() + err error + ctx = context.Background() + maxEntries = 100 + + processedMessageCount int ) const topicName = "my-topic-anon" @@ -63,21 +67,44 @@ func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publish session1, topic1 := getSessionAndTopic[string](g, topicName) defer session1.Close() - // create a subscriber first - sub1, err := topic1.CreateSubscriber(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Subscriber created", sub1) + go func() { + var ( + values []*subscriber.ReceiveResponse[string] + response *subscriber.CommitResponse + ) + sub1, err1 := topic1.CreateSubscriber(ctx) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + for { + values, err = sub1.Receive(context.Background()) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + if len(values) == 0 { + // nothing on topic + utils.Sleep(1) + continue + } + response, err = sub1.Commit(ctx, values[0].Channel, values[0].Position) + + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(response).ShouldNot(gomega.BeNil()) + g.Expect(response.Channel).Should(gomega.Equal(values[0].Channel)) + processedMessageCount++ + log.Println("Processed message count", processedMessageCount) + } + }() - pub1, err := topic1.CreatePublisher(context.Background()) + utils.Sleep(5) + + pub1, err := topic1.CreatePublisher(context.Background(), options...) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) - publishEntriesString(g, pub1, 1_000) + publishEntriesString(g, pub1, maxEntries) - utils.Sleep(5) + g.Eventually(func() int { return processedMessageCount }, 20*time.Second).Should(gomega.Equal(maxEntries)) - err = sub1.Close(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + utils.Sleep(5) err = pub1.Close(ctx) g.Expect(err).ShouldNot(gomega.HaveOccurred()) @@ -127,14 +154,14 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) } -func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E]) { +func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E], count int) { ctx := context.Background() pub1, err := topic1.CreatePublisher(context.Background()) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) - for i := 1; i <= 1_000; i++ { + for i := 1; i <= count; i++ { p := utils.Person{ ID: i, Name: fmt.Sprintf("my-value-%d", i), From 6bc3a6b9e48ea685b5866161ab99023cce64901e Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Tue, 29 Jul 2025 10:37:03 +0800 Subject: [PATCH 40/44] Topics progress - receive and commit --- test/e2e/topics/subscriber_test.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index 9cc72f95..ebc944f0 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -36,11 +36,7 @@ func TestSubscriberWithFilter(t *testing.T) { sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - - go func() { - - }() - + runTestPerson(g, topic1, sub1, maxMessages) } From c77c1d5d21ce3a1c9be41f25f8d5b404063eb02c Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 31 Jul 2025 11:41:26 +0800 Subject: [PATCH 41/44] Additional changes/ tests --- Makefile | 2 +- coherence/session.go | 2 + coherence/subscriber/subscriber.go | 17 +- coherence/topics.go | 131 +++++-- coherence/topics_events.go | 4 +- coherence/v1client.go | 6 +- .../src/main/resources/test-cache-config.xml | 9 + proto/v1/queue_service_messages_v1.pb.go | 125 +++++-- test/e2e/topics/subscriber_test.go | 345 ++++++++++++++++-- test/e2e/topics/topics_test.go | 75 +--- 10 files changed, 563 insertions(+), 153 deletions(-) diff --git a/Makefile b/Makefile index 90f67fba..41eed358 100644 --- a/Makefile +++ b/Makefile @@ -449,7 +449,7 @@ test-e2e-standalone-queues: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e # ---------------------------------------------------------------------------------------------------------------------- .PHONY: test-e2e-standalone-topics test-e2e-standalone-topics: test-clean test gotestsum $(BUILD_PROPS) ## Run e2e tests with Coherence queues - cd test && CGO_ENABLED=0 $(GOTESTSUM) --format testname --junitfile $(TEST_LOGS_DIR)/go-client-test-queues.xml \ + cd test && CGO_ENABLED=0 $(GOTESTSUM) --format testname --junitfile $(TEST_LOGS_DIR)/go-client-test-topics.xml \ -- $(GO_TEST_FLAGS) -v -coverprofile=$(COVERAGE_DIR)/cover-functional-topics.out -v ./e2e/topics/... -coverpkg=github.com/oracle/coherence-go-client/v2/coherence/... go tool cover -func=$(COVERAGE_DIR)/cover-functional-topics.out | grep -v '0.0%' diff --git a/coherence/session.go b/coherence/session.go index 8b45e902..83f553f7 100644 --- a/coherence/session.go +++ b/coherence/session.go @@ -276,6 +276,8 @@ func NewSession(ctx context.Context, options ...func(session *SessionOptions)) ( // setLogLevel sets the log level from the COHERENCE_LOG_LEVEL environment variable. func setLogLevel(envLevel string) { var level int + logLevelMutex.Lock() + defer logLevelMutex.Unlock() // try to convert from integer first if lvl, err := strconv.Atoi(envLevel); err == nil { diff --git a/coherence/subscriber/subscriber.go b/coherence/subscriber/subscriber.go index 855bd569..97df3479 100644 --- a/coherence/subscriber/subscriber.go +++ b/coherence/subscriber/subscriber.go @@ -37,6 +37,7 @@ type Options struct { Extractor []byte AutoCommit bool MaxMessages int32 + Channels []int32 } // CommitResponse represents th response from a [Subscriber] commit. @@ -82,13 +83,20 @@ func WithAutoCommit() func(options *Options) { } } -// WithMaxMessages returns a function to set the maximum messages for a[Subscriber]. +// WithMaxMessages returns a function to set the maximum messages for a [Subscriber]. func WithMaxMessages(maxMessages int32) func(options *Options) { return func(s *Options) { s.MaxMessages = maxMessages } } +// WithChannels returns a function to set the channels for a [Subscriber]. +func WithChannels(channels []int32) func(options *Options) { + return func(s *Options) { + s.Channels = channels + } +} + // WithTransformer returns a function to set the extractor [Subscriber]. func WithTransformer(extractor []byte) func(options *Options) { return func(s *Options) { @@ -97,5 +105,10 @@ func WithTransformer(extractor []byte) func(options *Options) { } func (o *Options) String() string { - return fmt.Sprintf("options{SubscriberGroup=%v, filter=%v}", o.SubscriberGroup, o.Filter) + var subGroup string + if o.SubscriberGroup != nil { + subGroup = *o.SubscriberGroup + } + return fmt.Sprintf("options{SubscriberGroup=%v, filter=%v, MaxMessages=%v, AutoCommit=%v, Transformer=%v, Channels=%v}", + subGroup, o.Filter, o.MaxMessages, o.AutoCommit, o.Extractor, o.Channels) } diff --git a/coherence/topics.go b/coherence/topics.go index 087b9dc0..34451819 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -71,7 +71,6 @@ type NamedTopic[V any] interface { CreateSubscriberGroup(ctx context.Context, subscriberGroup string, options ...func(o *subscribergroup.Options)) error // DestroySubscriberGroup destroys a subscriber group. - // TODO: Not viable to implement? DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error // AddLifecycleListener adds a [TopicLifecycleListener] to this topic. @@ -114,9 +113,13 @@ type Subscriber[V any] interface { // If the length of the [subscriber.ReceiveResponse] is zero this means no messages were available. Receive(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) - // Commit commits a channel and position meaning the message will be removed from the topic. + // Commit commits a channel and position meaning the message will not be get redelivered if the subscriber gets disconnected. Commit(ctx context.Context, channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) + // Peek attempts to peek messages without removing them. If the length of the [subscriber.ReceiveResponse] + //is zero this means no messages were available. + Peek(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) + // DestroySubscriberGroup destroys a subscriber group created by this subscriber. DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error @@ -296,7 +299,30 @@ func (bt *baseTopicsClient[V]) CreateSubscriberGroup(ctx context.Context, subscr } func (bt *baseTopicsClient[V]) DestroySubscriberGroup(ctx context.Context, subscriberGroup string) error { - return DestroySubscriberGroup(ctx, bt.session, subscriberGroup) + return DestroySubscriberGroup(ctx, bt.session, bt.topicID, subscriberGroup) +} + +func (bt *baseTopicsClient[V]) isTopicDestroyed() bool { + bt.mutex.RLock() + defer bt.mutex.RUnlock() + return bt.isDestroyed +} + +func (bt *baseTopicsClient[V]) setDestroyed() { + bt.mutex.Lock() + defer bt.mutex.Unlock() + bt.isDestroyed = true +} +func (bt *baseTopicsClient[V]) isTopicReleased() bool { + bt.mutex.RLock() + defer bt.mutex.RUnlock() + return bt.isReleased +} + +func (bt *baseTopicsClient[V]) setReleased() { + bt.mutex.Lock() + defer bt.mutex.Unlock() + bt.isReleased = true } func newPublisher[V any](session *Session, bt *baseTopicsClient[V], result *publisher.EnsurePublisherResult, topicName string, options *publisher.Options) (Publisher[V], error) { @@ -385,12 +411,12 @@ func closeSubscriber[V any](ctx context.Context, ts *topicSubscriber[V]) error { return ts.session.v1StreamManagerTopics.destroyPublisherOrSubscriber(ctx, ts.proxyID, pb1topics.TopicServiceRequestType_DestroySubscriber) } -func receiveInternal[V any](ctx context.Context, ts *topicSubscriber[V], maxMessages *int32) ([]*pb1topics.TopicElement, error) { +func peekOrReceiveInternal[V any](ctx context.Context, ts *topicSubscriber[V], maxMessages *int32, isReceive bool) ([]*pb1topics.TopicElement, error) { if ts.isClosed { return nil, ErrSubscriberClosed } - return ts.session.v1StreamManagerTopics.receive(ctx, ts.proxyID, maxMessages) + return ts.session.v1StreamManagerTopics.peekOrReceive(ctx, ts.proxyID, maxMessages, isReceive) } func commitInternal[V any](ctx context.Context, ts *topicSubscriber[V], channel int32, position *pb1topics.TopicPosition) (*subscriber.CommitResponse, error) { @@ -405,16 +431,24 @@ func (ts *topicSubscriber[V]) DestroySubscriberGroup(ctx context.Context, subscr return ts.session.v1StreamManagerTopics.destroySubscriberGroup(ctx, ts.proxyID, subscriberGroup) } -// Receive attempts to receive messages. Messages must be committed to be considered processed unless autoCommit is set. +func (ts *topicSubscriber[V]) Peek(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) { + return ts.peekOrReceive(ctx, false) +} + func (ts *topicSubscriber[V]) Receive(ctx context.Context) ([]*subscriber.ReceiveResponse[V], error) { - r, err := receiveInternal(ctx, ts, &ts.options.MaxMessages) + return ts.peekOrReceive(ctx, true) +} + +// Receive attempts to peekOrReceive messages. Messages must be committed to be considered processed unless autoCommit is set. +func (ts *topicSubscriber[V]) peekOrReceive(ctx context.Context, isReceive bool) ([]*subscriber.ReceiveResponse[V], error) { + r, err := peekOrReceiveInternal(ctx, ts, &ts.options.MaxMessages, isReceive) if err != nil { return nil, err } values, err := ts.decodeAll(r) if err != nil { - return nil, fmt.Errorf("unable to decode receive values: %v", err) + return nil, fmt.Errorf("unable to decode peek rr receive values: %v", err) } if ts.options.AutoCommit && len(values) > 0 { @@ -530,7 +564,6 @@ func CreatSubscriberWithTransformer[E any](ctx context.Context, session *Session func CreateSubscriber[V any](ctx context.Context, session *Session, topicName string, options ...func(cache *subscriber.Options)) (Subscriber[V], error) { var ( subscriberOptions = &subscriber.Options{} - binFilter []byte err error ) @@ -545,18 +578,15 @@ func CreateSubscriber[V any](ctx context.Context, session *Session, topicName st } } - if subscriberOptions.Filter != nil { - binFilter, err = session.genericSerializer.Serialize(subscriberOptions.Filter) - if err != nil { - return nil, err - } - } - - result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, subscriberOptions.SubscriberGroup, binFilter) + result, err := session.v1StreamManagerTopics.ensureSubscriber(ctx, topicName, subscriberOptions) if err != nil { return nil, err } + if result == nil { + return nil, fmt.Errorf("unable to create subscriber with options %v, check server logs: %v", subscriberOptions, err) + } + return newSubscriber[V](session, nil, result, topicName, subscriberOptions) } @@ -590,15 +620,14 @@ func CreateSubscriberGroup(ctx context.Context, session *Session, topicName stri } // DestroySubscriberGroup destroys a subscriber group. -func DestroySubscriberGroup(ctx context.Context, session *Session, subscriberGroup string) error { +func DestroySubscriberGroup(ctx context.Context, session *Session, proxyID int32, subscriberGroup string) error { if session.v1StreamManagerTopics == nil { if err := ensureV1StreamManagerTopics(session); err != nil { return err } } - // TODO: Is this valid???, how can we determine the proxyID if we don't have a subscriber - return session.v1StreamManagerTopics.destroySubscriberGroup(ctx, 0, subscriberGroup) + return session.v1StreamManagerTopics.destroySubscriberGroup(ctx, proxyID, subscriberGroup) } // ensurePublisher ensures a publisher. @@ -636,8 +665,8 @@ func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, } // ensureSubscriber ensures a subscriber. -func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, subscriberGroup *string, binFilter []byte) (*subscriber.EnsureSubscriberResult, error) { - req, err := m.newEnsureSubscriberRequest(topicName, subscriberGroup, binFilter) +func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string, options *subscriber.Options) (*subscriber.EnsureSubscriberResult, error) { + req, err := m.newEnsureSubscriberRequest(topicName, options) if err != nil { return nil, err } @@ -672,14 +701,26 @@ func (m *streamManagerV1) ensureSubscriber(ctx context.Context, topicName string return &s, nil } -// receive calls receive for a subscriber. -func (m *streamManagerV1) receive(ctx context.Context, proxyID int32, maxMessages *int32) ([]*pb1topics.TopicElement, error) { - req, err := m.newSubscriberReceiveRequest(proxyID, maxMessages) +// peekOrReceive calls peek or receive for a subscriber. +func (m *streamManagerV1) peekOrReceive(ctx context.Context, proxyID int32, maxMessages *int32, isReceive bool) ([]*pb1topics.TopicElement, error) { + var ( + messageType pb1topics.TopicServiceRequestType + err error + req *pb1.ProxyRequest + ) + if isReceive { + messageType = pb1topics.TopicServiceRequestType_Receive + req, err = m.newSubscriberReceiveRequest(proxyID, maxMessages) + } else { + messageType = pb1topics.TopicServiceRequestType_PeekAtPosition + req, err = m.newSubscriberPeekRequest(proxyID, maxMessages) + } + if err != nil { return nil, err } - requestType, err := m.submitTopicRequest(req, pb1topics.TopicServiceRequestType_SimpleReceive) + requestType, err := m.submitTopicRequest(req, messageType) newCtx, cancel := m.session.ensureContext(ctx) if cancel != nil { @@ -789,7 +830,7 @@ func (m *streamManagerV1) ensureSubscriberGroup(ctx context.Context, topicName s return err } -// destroySubscriberGroup destroys a subscriber. +// destroySubscriberGroup destroys a subscriber group. func (m *streamManagerV1) destroySubscriberGroup(ctx context.Context, proxyID int32, subscriberGroup string) error { req, err := m.newDestroySubscriberGroupRequest(proxyID, subscriberGroup) if err != nil { @@ -952,7 +993,7 @@ func ensureV1StreamManagerTopics(session *Session) error { } func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], destroy bool) error { - if bt.isDestroyed || bt.isReleased { + if bt.isTopicDestroyed() || bt.isTopicReleased() { return ErrTopicDestroyedOrReleased } @@ -969,12 +1010,12 @@ func releaseTopicInternal[V any](ctx context.Context, bt *baseTopicsClient[V], d if err != nil { return err } - bt.isDestroyed = true + bt.setDestroyed() bt.generateTopicLifecycleEvent(nil, TopicDestroyed) } else { if existingTopic, ok := bt.session.topics[bt.name]; ok { bt.generateTopicLifecycleEvent(existingTopic, TopicReleased) - bt.isReleased = true + bt.setReleased() } } @@ -1009,11 +1050,25 @@ func (m *streamManagerV1) newDestroyPublisherOrSubscriberRequest(proxyID int32, return m.newWrapperProxyTopicsRequest("", requestType, anyReq) } -func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, subscriberGroup *string, binFilter []byte) (*pb1.ProxyRequest, error) { +func (m *streamManagerV1) newEnsureSubscriberRequest(topicName string, options *subscriber.Options) (*pb1.ProxyRequest, error) { + var ( + err error + binFilter []byte + ) + + if options.Filter != nil { + binFilter, err = m.session.genericSerializer.Serialize(options.Filter) + if err != nil { + return nil, err + } + } + req := &pb1topics.EnsureSimpleSubscriberRequest{ Topic: topicName, Filter: binFilter, - SubscriberGroup: subscriberGroup, + SubscriberGroup: options.SubscriberGroup, + Extractor: options.Extractor, + Channels: options.Channels, } anyReq, err := anypb.New(req) @@ -1034,6 +1089,18 @@ func (m *streamManagerV1) newSubscriberReceiveRequest(ProxyID int32, maxMessages } return m.newWrapperProxyPublisherRequest(ProxyID, pb1topics.TopicServiceRequestType_SimpleReceive, anyReq) } +func (m *streamManagerV1) newSubscriberPeekRequest(ProxyID int32, maxMessages *int32) (*pb1.ProxyRequest, error) { + req := &pb1topics.SimpleReceiveRequest{ + MaxMessages: maxMessages, + } + + anyReq, err := anypb.New(req) + if err != nil { + return nil, err + } + // TODO: FIX THIS when SImplePeek available + return m.newWrapperProxyPublisherRequest(ProxyID, pb1topics.TopicServiceRequestType_SimpleReceive, anyReq) +} func (m *streamManagerV1) newSubscriberCommitRequest(ProxyID int32, channel int32, position *pb1topics.TopicPosition) (*pb1.ProxyRequest, error) { req := &pb1topics.ChannelAndPosition{ diff --git a/coherence/topics_events.go b/coherence/topics_events.go index 17ae5ffa..943c8fd5 100644 --- a/coherence/topics_events.go +++ b/coherence/topics_events.go @@ -99,7 +99,7 @@ func (t *topicLifecycleListener[V]) on(event TopicLifecycleEventType, callback f } func (bt *baseTopicsClient[V]) AddLifecycleListener(listener TopicLifecycleListener[V]) error { - if bt.isDestroyed || bt.isReleased { + if bt.isTopicDestroyed() || bt.isTopicReleased() { return ErrTopicDestroyedOrReleased } @@ -108,7 +108,7 @@ func (bt *baseTopicsClient[V]) AddLifecycleListener(listener TopicLifecycleListe } func (bt *baseTopicsClient[V]) RemoveLifecycleListener(listener TopicLifecycleListener[V]) error { - if bt.isDestroyed || bt.isReleased { + if bt.isTopicDestroyed() || bt.isTopicReleased() { return ErrTopicDestroyedOrReleased } diff --git a/coherence/v1client.go b/coherence/v1client.go index a38375b8..d270332c 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -37,6 +37,7 @@ var ( ALL logLevel = 5 // all messages // current log level + logLevelMutex sync.RWMutex currentLogLevel int ) @@ -251,6 +252,9 @@ func (m *streamManagerV1) processResponseMessage(id int64, resp *responseMessage // logMessage logs a message only if the level <= currentLogLevel func logMessage(level logLevel, format string, args ...any) { + logLevelMutex.RLock() + defer logLevelMutex.RUnlock() + if int(level) <= currentLogLevel { log.Println(getLogMessage(level, format, args...)) } @@ -561,7 +565,7 @@ func (m *streamManagerV1) ensure(ctx context.Context, name string, IDMap safeMap return nil, err } - // store the cache id in the session , no lock required as already locked + // store the cache/topic/queue id in the session, no lock required as already locked ID = &message.Value IDMap.Add(name, *ID) diff --git a/java/coherence-go-test/src/main/resources/test-cache-config.xml b/java/coherence-go-test/src/main/resources/test-cache-config.xml index 2ea736b9..8b965f7e 100644 --- a/java/coherence-go-test/src/main/resources/test-cache-config.xml +++ b/java/coherence-go-test/src/main/resources/test-cache-config.xml @@ -32,6 +32,15 @@ * topic-scheme + + durable-* + topic-scheme + + + subscriber-group + + + diff --git a/proto/v1/queue_service_messages_v1.pb.go b/proto/v1/queue_service_messages_v1.pb.go index f1a6d9af..445035bd 100644 --- a/proto/v1/queue_service_messages_v1.pb.go +++ b/proto/v1/queue_service_messages_v1.pb.go @@ -1,5 +1,5 @@ // -// Copyright (c) 2020, 2024, Oracle and/or its affiliates. +// Copyright (c) 2020, 2025, Oracle and/or its affiliates. // // Licensed under the Universal Permissive License v 1.0 as shown at // https://oss.oracle.com/licenses/upl. @@ -112,6 +112,20 @@ const ( // The message field should not be set. // The response will contain the Queue Id and an Int32Value in the response field. NamedQueueRequestType_Size NamedQueueRequestType = 12 + // Add a value to the tail of a Queue. + // The message field must contain a OfferRequest that contains the + // serialized value and other attributes. + // The response will contain the Queue Id and a QueueOfferResult containing the + // result of the offer operation. + NamedQueueRequestType_ExtendedOfferTail NamedQueueRequestType = 13 + // Add a value to the head of a Deque. + // This method is only supported for a double ended Deque. + // The queue type used to ensure the queue must have been a Deque + // The message field must contain a OfferRequest that contains the + // serialized value and other attributes. + // The response will contain the Queue Id and a QueueOfferResult containing the + // result of the offer operation. + NamedQueueRequestType_ExtendedOfferHead NamedQueueRequestType = 14 ) // Enum value maps for NamedQueueRequestType. @@ -130,21 +144,25 @@ var ( 10: "PollTail", 11: "PeekTail", 12: "Size", + 13: "ExtendedOfferTail", + 14: "ExtendedOfferHead", } NamedQueueRequestType_value = map[string]int32{ - "Unknown": 0, - "Clear": 1, - "Destroy": 2, - "EnsureQueue": 3, - "IsEmpty": 4, - "IsReady": 5, - "OfferTail": 6, - "OfferHead": 7, - "PollHead": 8, - "PeekHead": 9, - "PollTail": 10, - "PeekTail": 11, - "Size": 12, + "Unknown": 0, + "Clear": 1, + "Destroy": 2, + "EnsureQueue": 3, + "IsEmpty": 4, + "IsReady": 5, + "OfferTail": 6, + "OfferHead": 7, + "PollHead": 8, + "PeekHead": 9, + "PollTail": 10, + "PeekTail": 11, + "Size": 12, + "ExtendedOfferTail": 13, + "ExtendedOfferHead": 14, } ) @@ -540,6 +558,65 @@ func (x *QueueOfferResult) GetIndex() int64 { return 0 } +// A request to offer a value to a queue. +type OfferRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The serialized value to offer to the queue. + Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + // The number of milliseconds until the queue entry will + // expire, also referred to as the entry's "time to live"; + // pass zero (0) to use the queue's default + // time-to-live setting; pass minus one (-1) to + // indicate that the queue entry should never expire + Ttl int64 `protobuf:"varint,2,opt,name=ttl,proto3" json:"ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OfferRequest) Reset() { + *x = OfferRequest{} + mi := &file_queue_service_messages_v1_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OfferRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OfferRequest) ProtoMessage() {} + +func (x *OfferRequest) ProtoReflect() protoreflect.Message { + mi := &file_queue_service_messages_v1_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OfferRequest.ProtoReflect.Descriptor instead. +func (*OfferRequest) Descriptor() ([]byte, []int) { + return file_queue_service_messages_v1_proto_rawDescGZIP(), []int{4} +} + +func (x *OfferRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *OfferRequest) GetTtl() int64 { + if x != nil { + return x.Ttl + } + return 0 +} + var File_queue_service_messages_v1_proto protoreflect.FileDescriptor const file_queue_service_messages_v1_proto_rawDesc = "" + @@ -564,7 +641,10 @@ const file_queue_service_messages_v1_proto_rawDesc = "" + "\x04type\x18\x02 \x01(\x0e2-.coherence.concurrent.queue.v1.NamedQueueTypeR\x04type\"F\n" + "\x10QueueOfferResult\x12\x1c\n" + "\tsucceeded\x18\x01 \x01(\bR\tsucceeded\x12\x14\n" + - "\x05index\x18\x02 \x01(\x03R\x05index*\xc7\x01\n" + + "\x05index\x18\x02 \x01(\x03R\x05index\"6\n" + + "\fOfferRequest\x12\x14\n" + + "\x05value\x18\x01 \x01(\fR\x05value\x12\x10\n" + + "\x03ttl\x18\x02 \x01(\x03R\x03ttl*\xf5\x01\n" + "\x15NamedQueueRequestType\x12\v\n" + "\aUnknown\x10\x00\x12\t\n" + "\x05Clear\x10\x01\x12\v\n" + @@ -579,7 +659,9 @@ const file_queue_service_messages_v1_proto_rawDesc = "" + "\bPollTail\x10\n" + "\x12\f\n" + "\bPeekTail\x10\v\x12\b\n" + - "\x04Size\x10\f*S\n" + + "\x04Size\x10\f\x12\x15\n" + + "\x11ExtendedOfferTail\x10\r\x12\x15\n" + + "\x11ExtendedOfferHead\x10\x0e*S\n" + "\x16NamedQueueResponseType\x12\v\n" + "\aMessage\x10\x00\x12\x0e\n" + "\n" + @@ -606,7 +688,7 @@ func file_queue_service_messages_v1_proto_rawDescGZIP() []byte { } var file_queue_service_messages_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_queue_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_queue_service_messages_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_queue_service_messages_v1_proto_goTypes = []any{ (NamedQueueRequestType)(0), // 0: coherence.concurrent.queue.v1.NamedQueueRequestType (NamedQueueResponseType)(0), // 1: coherence.concurrent.queue.v1.NamedQueueResponseType @@ -615,13 +697,14 @@ var file_queue_service_messages_v1_proto_goTypes = []any{ (*NamedQueueResponse)(nil), // 4: coherence.concurrent.queue.v1.NamedQueueResponse (*EnsureQueueRequest)(nil), // 5: coherence.concurrent.queue.v1.EnsureQueueRequest (*QueueOfferResult)(nil), // 6: coherence.concurrent.queue.v1.QueueOfferResult - (*anypb.Any)(nil), // 7: google.protobuf.Any + (*OfferRequest)(nil), // 7: coherence.concurrent.queue.v1.OfferRequest + (*anypb.Any)(nil), // 8: google.protobuf.Any } var file_queue_service_messages_v1_proto_depIdxs = []int32{ 0, // 0: coherence.concurrent.queue.v1.NamedQueueRequest.type:type_name -> coherence.concurrent.queue.v1.NamedQueueRequestType - 7, // 1: coherence.concurrent.queue.v1.NamedQueueRequest.message:type_name -> google.protobuf.Any + 8, // 1: coherence.concurrent.queue.v1.NamedQueueRequest.message:type_name -> google.protobuf.Any 1, // 2: coherence.concurrent.queue.v1.NamedQueueResponse.type:type_name -> coherence.concurrent.queue.v1.NamedQueueResponseType - 7, // 3: coherence.concurrent.queue.v1.NamedQueueResponse.message:type_name -> google.protobuf.Any + 8, // 3: coherence.concurrent.queue.v1.NamedQueueResponse.message:type_name -> google.protobuf.Any 2, // 4: coherence.concurrent.queue.v1.EnsureQueueRequest.type:type_name -> coherence.concurrent.queue.v1.NamedQueueType 5, // [5:5] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type @@ -644,7 +727,7 @@ func file_queue_service_messages_v1_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_queue_service_messages_v1_proto_rawDesc), len(file_queue_service_messages_v1_proto_rawDesc)), NumEnums: 3, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index ebc944f0..eed3af62 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -8,23 +8,34 @@ package topics import ( "context" + "errors" + "fmt" "github.com/onsi/gomega" "github.com/oracle/coherence-go-client/v2/coherence" "github.com/oracle/coherence-go-client/v2/coherence/extractors" "github.com/oracle/coherence-go-client/v2/coherence/filters" + "github.com/oracle/coherence-go-client/v2/coherence/publisher" "github.com/oracle/coherence-go-client/v2/coherence/subscriber" "github.com/oracle/coherence-go-client/v2/coherence/subscribergroup" "github.com/oracle/coherence-go-client/v2/test/utils" "log" + "sync" "testing" + "time" ) +var finishedMutex sync.Mutex + func TestSubscriberWithFilter(t *testing.T) { var ( - g = gomega.NewWithT(t) - err error - ctx = context.Background() - maxMessages = 1000 + g = gomega.NewWithT(t) + err error + ctx = context.Background() + messageCount = 100 + actualCount counter + timeout = 60 * time.Second + expectedCount = 10 + finished bool ) const topicName = "my-topic-anon-filter" @@ -33,69 +44,111 @@ func TestSubscriberWithFilter(t *testing.T) { defer session1.Close() // create a subscriber with a filter - sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + sub1, err := topic1.CreateSubscriber(ctx, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 20))) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - - runTestPerson(g, topic1, sub1, maxMessages) + + go func() { + results, err1 := receiveMessages(sub1, expectedCount, timeout, &finished, nil) + log.Printf("Received %d messages: %v", len(results), results) + actualCount.Set(len(results)) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + g.Expect(results).ShouldNot(gomega.BeEmpty()) + g.Expect(len(results)).Should(gomega.Equal(10)) + }() + + utils.Sleep(5) + + go runTestPerson(g, topic1, messageCount) + + utils.Sleep(5) + + g.Eventually(func() int { return actualCount.Get() }, timeout+(time.Second*5)).Should(gomega.Equal(expectedCount)) } func TestSubscriberWithTransformer(t *testing.T) { var ( - g = gomega.NewWithT(t) - err error - ctx = context.Background() + g = gomega.NewWithT(t) + err error + ctx = context.Background() + messageCount = 1_000 + actualCount counter + timeout = 60 * time.Second + finished bool ) const topicName = "my-topic-anon-transformer" session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) - defer session1.Close() + defer func() { + _ = topic1.Destroy(ctx) + session1.Close() + }() extractor := extractors.Extract[string]("name") - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, - subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 10))) + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) - runTestPerson(g, topic1, sub1, 1_000) + go func() { + results, err1 := receiveMessages(sub1, messageCount, timeout, &finished, nil) + actualCount.Set(len(results)) + log.Printf("Received %d messages: %v", len(results), results) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + g.Expect(results).ShouldNot(gomega.BeEmpty()) + g.Expect(len(results)).Should(gomega.Equal(messageCount)) + }() + + utils.Sleep(5) + + runTestPerson(g, topic1, messageCount) + + utils.Sleep(5) + + g.Eventually(func() int { return actualCount.Get() }, timeout+(time.Second*5)).Should(gomega.Equal(messageCount)) } func TestSubscriberWithTransformerAndFilter(t *testing.T) { var ( - g = gomega.NewWithT(t) - err error - ctx = context.Background() + g = gomega.NewWithT(t) + err error + ctx = context.Background() + messageCount = 100 + actualCount counter + timeout = 60 * time.Second + finished bool ) - const topicName = "my-topic-anon-transformer" + const topicName = "my-topic-anon-transformer-filter" session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) - defer session1.Close() + defer func() { + _ = topic1.Destroy(ctx) + session1.Close() + }() extractor := extractors.Extract[string]("name") - // create a subscriber with a transformer, this - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Subscriber created", sub1) - pub1, err := topic1.CreatePublisher(context.Background()) + sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, + subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 20))) g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Publisher created", pub1) + log.Println("Subscriber created", sub1) - publishEntriesPerson(g, pub1, 1_000) + go func() { + results, err1 := receiveMessages(sub1, 10, time.Second*30, &finished, nil) + actualCount.Set(len(results)) + log.Printf("Received %d messages: %v", len(results), results) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + g.Expect(results).ShouldNot(gomega.BeEmpty()) + g.Expect(len(results)).Should(gomega.Equal(10)) + }() utils.Sleep(5) - err = sub1.Close(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - - err = sub1.Close(ctx) - g.Expect(err).Should(gomega.HaveOccurred()) + runTestPerson(g, topic1, messageCount) - err = topic1.Destroy(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Eventually(func() int { return actualCount.Get() }, timeout+(time.Second*5)).Should(gomega.Equal(10)) } func TestSubscriberGroupWithinTopic(t *testing.T) { @@ -111,7 +164,10 @@ func TestSubscriberGroupWithinTopic(t *testing.T) { ) session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) - defer session1.Close() + defer func() { + _ = topic1.Destroy(ctx) + session1.Close() + }() err = topic1.CreateSubscriberGroup(ctx, subGroup) g.Expect(err).ShouldNot(gomega.HaveOccurred()) @@ -161,3 +217,224 @@ func runTestSubscriberGroup(g *gomega.WithT, subscriberGroup string, options ... g.Expect(topic1.Destroy(ctx)).ShouldNot(gomega.HaveOccurred()) } + +type counter struct { + sync.Mutex + value int +} + +func (c *counter) Set(value int) { + c.Lock() + defer c.Unlock() + c.value = value +} + +func (c *counter) Get() int { + c.Lock() + defer c.Unlock() + return c.value +} + +func (c *counter) Increment() { + c.Lock() + defer c.Unlock() + c.value++ +} + +func TestMultipleSubscribers(t *testing.T) { + var ( + allChannels = []int32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + g = gomega.NewWithT(t) + emptySubOptions []func(*subscriber.Options) + emptyPubOptions []func(*publisher.Options) + pub23Channels = []func(*publisher.Options){publisher.WithChannelCount(23)} + subGroupOptions = []func(*subscriber.Options){subscriber.InSubscriberGroup("group1")} + subGroupOptions2 = []func(*subscriber.Options){subscriber.InSubscriberGroup("group1"), subscriber.WithMaxMessages(10)} + subGroupOptions2Channels = []func(*subscriber.Options){subscriber.InSubscriberGroup("group1"), subscriber.WithChannels(allChannels)} + ) + + testCases := []struct { + testName string + g *gomega.WithT + subscriberCount int + subOptions []func(*subscriber.Options) + pubOptions []func(*publisher.Options) + test func(testName string, g *gomega.WithT, subscriberCount int, subOptions []func(o *subscriber.Options), pubOptions []func(o *publisher.Options)) + }{ + {"SubscribersSubGroup2SubscribersMultiple", g, 2, subGroupOptions2, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersSubGroup2SubscribersMultipleChannels", g, 2, subGroupOptions2Channels, emptyPubOptions, RunTestMultipleSubscribers}, + + {"SubscribersSubGroup2Subscribers", g, 2, subGroupOptions, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersSubGroup6Subscribers", g, 6, subGroupOptions, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersSubGroup18Subscribers", g, 18, subGroupOptions, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersSubGroup23Channels12Subscribers", g, 4, subGroupOptions, pub23Channels, RunTestMultipleSubscribers}, + + {"SubscribersNoSubGroup2Subscribers", g, 2, emptySubOptions, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersNoSubGroup6Subscribers", g, 6, emptySubOptions, emptyPubOptions, RunTestMultipleSubscribers}, + {"SubscribersNoSubGroup12Subscribers", g, 12, emptySubOptions, emptyPubOptions, RunTestMultipleSubscribers}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + tc.test(tc.testName, g, tc.subscriberCount, tc.subOptions, tc.pubOptions) + }) + } +} + +func RunTestMultipleSubscribers(testName string, g *gomega.WithT, subscriberCount int, + subOptions []func(o *subscriber.Options), pubOptions []func(o *publisher.Options)) { + var ( + ctx = context.Background() + receiveCounts = make([]int, subscriberCount) + timeout = time.Second * 600 + maxMessages = 10_000 + subscriberOptions = getSubscriberOptions(subOptions...) + finished = false + mutex sync.Mutex + wg sync.WaitGroup + topicName = fmt.Sprintf("my-topic-%s", testName) + hasSubscriberGroup bool + ) + + session1, topic1 := getSessionAndTopic[utils.Person](g, topicName) + defer func() { + _ = topic1.Destroy(ctx) + session1.Close() + }() + + wg.Add(subscriberCount) + + for i := 0; i < subscriberCount; i++ { + go func(subscriberID int) { + sub1, err1 := topic1.CreateSubscriber(ctx, subOptions...) + defer func() { + _ = sub1.Close(ctx) + }() + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + log.Println("Subscriber created", sub1) + + expectedCount := maxMessages + if subscriberOptions.SubscriberGroup != nil { + // reset expectedCount to 0 which means it will wait for the timeout rather than the count as + // with subscriber groups, the messages are only received by one subscriber, not all + expectedCount = 0 + hasSubscriberGroup = true + } + + // signal that the subscriber has been created and is ready + wg.Done() + results, err1 := receiveMessages(sub1, expectedCount, timeout, &finished, &receiveCounts[subscriberID]) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) + mutex.Lock() + receiveCounts[subscriberID] = len(results) + mutex.Unlock() + log.Printf("Subscriber %v received %d messages", subscriberID, receiveCounts[subscriberID]) + }(i) + } + + wg.Wait() + log.Println("all subscribers ready, sleeping") + + utils.Sleep(5) + + go runTestPerson(g, topic1, maxMessages, pubOptions...) + + valueToMatch := maxMessages + if !hasSubscriberGroup { + valueToMatch = maxMessages * subscriberCount + } + + g.Eventually(func() int { + mutex.Lock() + defer mutex.Unlock() + + count := 0 + for _, c := range receiveCounts { + count += c + } + + if hasSubscriberGroup { + // this means the total count should == maxMessages as messages are only consumed + // by one subscriber in the subscriber group + if count == maxMessages { + setFinished(&finished) + } + } else if count == maxMessages*subscriberCount { + // should be subscriber count * maxMessages as each subscriber receives each message + setFinished(&finished) + } + + return count + }, timeout+(time.Second*10)).Should(gomega.Equal(valueToMatch)) +} + +func getSubscriberOptions(options ...func(*subscriber.Options)) *subscriber.Options { + var subscriberOptions = &subscriber.Options{} + + // apply any subscriber options + for _, f := range options { + f(subscriberOptions) + } + + return subscriberOptions +} + +func isFinished(finished *bool) bool { + finishedMutex.Lock() + defer finishedMutex.Unlock() + return finished != nil && *finished +} + +func setFinished(finished *bool) { + finishedMutex.Lock() + defer finishedMutex.Unlock() + *finished = true +} + +func receiveMessages[T any](sub coherence.Subscriber[T], expectedCount int, timeout time.Duration, finished *bool, count *int) ([]T, error) { + var ( + start = time.Now() + results = make([]T, 0) + messageCounter = 0 + ) + + for { + if time.Since(start) > timeout || *finished { + if expectedCount > 0 { + return results, errors.New("timeout") + } + return results, nil + } + + // attempt to receive + r, err := sub.Receive(context.Background()) + if err != nil { + return results, err + } + + if len(r) == 0 { + // nothing, so sleep a while + time.Sleep(time.Millisecond * 250) + continue + } + + for _, msg := range r { + results = append(results, *msg.Value) + messageCounter++ + if count != nil { + *count = messageCounter + } + } + + lastChannel := r[len(r)-1].Channel + lastPosition := r[len(r)-1].Position + + _, err = sub.Commit(context.Background(), lastChannel, lastPosition) + if err != nil { + return nil, err + } + + if (expectedCount != 0 && messageCounter == expectedCount) || isFinished(finished) { + return results, nil + } + } +} diff --git a/test/e2e/topics/topics_test.go b/test/e2e/topics/topics_test.go index 46dbe02a..ebc5d08d 100644 --- a/test/e2e/topics/topics_test.go +++ b/test/e2e/topics/topics_test.go @@ -47,22 +47,22 @@ func TestBasicTopicCreatedAndDestroy(t *testing.T) { func TestTopicPublish(t *testing.T) { g := gomega.NewWithT(t) - RunTestBasicTopicAnonPubSub(g) - RunTestBasicTopicAnonPubSub(g, publisher.WithDefaultOrdering()) - RunTestBasicTopicAnonPubSub(g, publisher.WithRoundRobinOrdering()) - RunTestBasicTopicAnonPubSub(g, publisher.WithChannelCount(21)) + RunTestBasicTopicAnonPubSub(g, "-1") + RunTestBasicTopicAnonPubSub(g, "-2", publisher.WithDefaultOrdering()) + RunTestBasicTopicAnonPubSub(g, "-3", publisher.WithRoundRobinOrdering()) + RunTestBasicTopicAnonPubSub(g, "-4", publisher.WithChannelCount(21)) } -func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publisher.Options)) { +func RunTestBasicTopicAnonPubSub(g *gomega.WithT, suffix string, options ...func(cache *publisher.Options)) { var ( err error ctx = context.Background() maxEntries = 100 - processedMessageCount int + processedMessageCount counter ) - const topicName = "my-topic-anon" + var topicName = fmt.Sprintf("my-topic-anon%s", suffix) session1, topic1 := getSessionAndTopic[string](g, topicName) defer session1.Close() @@ -77,20 +77,19 @@ func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publish log.Println("Subscriber created", sub1) for { - values, err = sub1.Receive(context.Background()) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + values, err1 = sub1.Receive(context.Background()) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) if len(values) == 0 { // nothing on topic utils.Sleep(1) continue } - response, err = sub1.Commit(ctx, values[0].Channel, values[0].Position) + response, err1 = sub1.Commit(ctx, values[0].Channel, values[0].Position) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(err1).ShouldNot(gomega.HaveOccurred()) g.Expect(response).ShouldNot(gomega.BeNil()) g.Expect(response.Channel).Should(gomega.Equal(values[0].Channel)) - processedMessageCount++ - log.Println("Processed message count", processedMessageCount) + processedMessageCount.Increment() } }() @@ -102,7 +101,7 @@ func RunTestBasicTopicAnonPubSub(g *gomega.WithT, options ...func(cache *publish publishEntriesString(g, pub1, maxEntries) - g.Eventually(func() int { return processedMessageCount }, 20*time.Second).Should(gomega.Equal(maxEntries)) + g.Eventually(func() int { return processedMessageCount.Get() }, 120*time.Second).Should(gomega.Equal(maxEntries)) utils.Sleep(5) @@ -154,10 +153,10 @@ func TestCreatePubSubWithoutCreatingTopic(t *testing.T) { g.Expect(err).ShouldNot(gomega.HaveOccurred()) } -func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], s coherence.Subscriber[E], count int) { +func runTestPerson(g *gomega.WithT, topic1 coherence.NamedTopic[utils.Person], count int, options ...func(options *publisher.Options)) { ctx := context.Background() - pub1, err := topic1.CreatePublisher(context.Background()) + pub1, err := topic1.CreatePublisher(context.Background(), options...) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Publisher created", pub1) @@ -171,50 +170,6 @@ func runTestPerson[E any](g *gomega.WithT, topic1 coherence.NamedTopic[utils.Per g.Expect(err2).ShouldNot(gomega.HaveOccurred()) g.Expect(status).ShouldNot(gomega.BeNil()) } - - utils.Sleep(5) - - err = s.Close(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - - err = s.Close(ctx) - g.Expect(err).Should(gomega.HaveOccurred()) -} - -func runTestString[E any](g *gomega.WithT, topic1 coherence.NamedTopic[string], s coherence.Subscriber[E]) { - ctx := context.Background() - - pub1, err := topic1.CreatePublisher(context.Background()) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - log.Println("Publisher created", pub1) - - for i := 1; i <= 1_000; i++ { - status, err2 := pub1.Publish(ctx, fmt.Sprintf("my-value-%d", i)) - g.Expect(err2).ShouldNot(gomega.HaveOccurred()) - g.Expect(status).ShouldNot(gomega.BeNil()) - } - - utils.Sleep(5) - - err = s.Close(ctx) - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - - err = s.Close(ctx) - g.Expect(err).Should(gomega.HaveOccurred()) -} - -func publishEntriesPerson(g *gomega.WithT, pub coherence.Publisher[utils.Person], count int) { - ctx := context.Background() - for i := 1; i <= count; i++ { - p := utils.Person{ - ID: i, - Name: fmt.Sprintf("my-value-%d", i), - Age: 10 + i, - } - status, err2 := pub.Publish(ctx, p) - g.Expect(err2).ShouldNot(gomega.HaveOccurred()) - g.Expect(status).ShouldNot(gomega.BeNil()) - } } func publishEntriesString(g *gomega.WithT, pub coherence.Publisher[string], count int) { From abe5ace3aa6df886ffb518a3cefd8bb6ec24a632 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 31 Jul 2025 11:44:40 +0800 Subject: [PATCH 42/44] CI/CD updates --- .github/workflows/build-v1.yaml | 1 - .github/workflows/examples-jakarta-v1.2.2.yaml | 1 - .github/workflows/examples-v1.2.2.yaml | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/workflows/build-v1.yaml b/.github/workflows/build-v1.yaml index 1ffd9120..477df523 100644 --- a/.github/workflows/build-v1.yaml +++ b/.github/workflows/build-v1.yaml @@ -27,7 +27,6 @@ jobs: coherence-version: - 25.03.1 - 25.03.2 - - 25.03.3-SNAPSHOT # Checkout the source, we need a depth of zero to fetch all of the history otherwise # the copyright check cannot work out the date of the files from Git. diff --git a/.github/workflows/examples-jakarta-v1.2.2.yaml b/.github/workflows/examples-jakarta-v1.2.2.yaml index 20fd3202..c81b7c2a 100644 --- a/.github/workflows/examples-jakarta-v1.2.2.yaml +++ b/.github/workflows/examples-jakarta-v1.2.2.yaml @@ -25,7 +25,6 @@ jobs: coherenceVersion: - 25.03.1 - 25.03.2 - - 25.03.3-SNAPSHOT go-version: - 1.21.x - 1.22.x diff --git a/.github/workflows/examples-v1.2.2.yaml b/.github/workflows/examples-v1.2.2.yaml index 4e149aa6..833c9a49 100644 --- a/.github/workflows/examples-v1.2.2.yaml +++ b/.github/workflows/examples-v1.2.2.yaml @@ -23,7 +23,6 @@ jobs: fail-fast: false matrix: coherenceVersion: - - 22.06.14-SNAPSHOT - 22.06.13 go-version: - 1.21.x From 1a10901aac1a39b064390d9dbef8b91fefa0195c Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 31 Jul 2025 12:17:49 +0800 Subject: [PATCH 43/44] Minor typos --- coherence/publisher_events.go | 4 ++-- coherence/subscriber_events.go | 2 +- coherence/topics.go | 4 ++-- test/e2e/topics/subscriber_test.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/coherence/publisher_events.go b/coherence/publisher_events.go index a4cf8555..20ba0ec9 100644 --- a/coherence/publisher_events.go +++ b/coherence/publisher_events.go @@ -46,7 +46,7 @@ func (l *publisherLifecycleEvent[V]) Source() Publisher[V] { // String returns a string representation of a [PublisherLifecycleEvent].. func (l *publisherLifecycleEvent[V]) String() string { - return fmt.Sprintf("spublisherLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) + return fmt.Sprintf("publisherLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) } // PublisherLifecycleListener allows registering callbacks to be notified when lifecycle events @@ -88,7 +88,7 @@ func (t *publisherLifecycleListener[V]) OnAny(callback func(PublisherLifecycleEv OnDisconnected(callback).OnChannelsFreed(callback) } -// OnDestroyed registers a callback that will be notified when a [SubscPublisherriber] is destroyed. +// OnDestroyed registers a callback that will be notified when a [Publisher] is destroyed. func (t *publisherLifecycleListener[V]) OnDestroyed(callback func(PublisherLifecycleEvent[V])) PublisherLifecycleListener[V] { return t.on(PublisherDestroyed, callback) } diff --git a/coherence/subscriber_events.go b/coherence/subscriber_events.go index dd6a95f2..b90064af 100644 --- a/coherence/subscriber_events.go +++ b/coherence/subscriber_events.go @@ -49,7 +49,7 @@ func (l *subscriberLifecycleEvent[V]) Source() Subscriber[V] { return l.source } -// String returns a string representation of a [SubscriberLifecycleEvent].\. +// String returns a string representation of a [SubscriberLifecycleEvent]. func (l *subscriberLifecycleEvent[V]) String() string { return fmt.Sprintf("subscriberLifecycleEvent{source=%v, type=%s}", l.Source(), l.Type()) } diff --git a/coherence/topics.go b/coherence/topics.go index 34451819..030690f5 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -545,9 +545,9 @@ func CreatePublisher[V any](ctx context.Context, session *Session, topicName str return newPublisher[V](session, nil, result, topicName, publisherOptions) } -// CreatSubscriberWithTransformer creates a subscriber which will transform the value from the topic using +// CreateSubscriberWithTransformer creates a subscriber which will transform the value from the topic using // the supplied extractor. -func CreatSubscriberWithTransformer[E any](ctx context.Context, session *Session, topicName string, +func CreateSubscriberWithTransformer[E any](ctx context.Context, session *Session, topicName string, extractor extractors.ValueExtractor[any, E], options ...func(cache *subscriber.Options)) (Subscriber[E], error) { binExtractor, err := session.genericSerializer.Serialize(extractor) diff --git a/test/e2e/topics/subscriber_test.go b/test/e2e/topics/subscriber_test.go index eed3af62..3eec15bd 100644 --- a/test/e2e/topics/subscriber_test.go +++ b/test/e2e/topics/subscriber_test.go @@ -87,7 +87,7 @@ func TestSubscriberWithTransformer(t *testing.T) { extractor := extractors.Extract[string]("name") - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor) + sub1, err := coherence.CreateSubscriberWithTransformer(ctx, session1, topicName, extractor) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) @@ -130,7 +130,7 @@ func TestSubscriberWithTransformerAndFilter(t *testing.T) { extractor := extractors.Extract[string]("name") - sub1, err := coherence.CreatSubscriberWithTransformer(ctx, session1, topicName, extractor, + sub1, err := coherence.CreateSubscriberWithTransformer(ctx, session1, topicName, extractor, subscriber.WithFilter(filters.GreaterEqual(extractors.Extract[int]("age"), 20))) g.Expect(err).ShouldNot(gomega.HaveOccurred()) log.Println("Subscriber created", sub1) From b91ced2f7f918dc8b1eb7615bcd3587a792930d2 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 31 Jul 2025 12:21:18 +0800 Subject: [PATCH 44/44] Minor --- coherence/topics.go | 6 +++--- coherence/v1client.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coherence/topics.go b/coherence/topics.go index 030690f5..2318aaba 100644 --- a/coherence/topics.go +++ b/coherence/topics.go @@ -653,7 +653,7 @@ func (m *streamManagerV1) ensurePublisher(ctx context.Context, topicName string, var message = &pb1topics.EnsurePublisherResponse{} if err = result.UnmarshalTo(message); err != nil { - err = getUnmarshallError("ensure response", err) + err = getUnmarshallError("ensure publisher response", err) return nil, err } @@ -933,7 +933,7 @@ func (m *streamManagerV1) ensureTopicChannels(ctx context.Context, topicName str var message = &wrapperspb.Int32Value{} if err = result.UnmarshalTo(message); err != nil { - err = getUnmarshallError("ensure response", err) + err = getUnmarshallError("ensure topics channels response", err) return nil, err } @@ -1098,7 +1098,7 @@ func (m *streamManagerV1) newSubscriberPeekRequest(ProxyID int32, maxMessages *i if err != nil { return nil, err } - // TODO: FIX THIS when SImplePeek available + // TODO: FIX THIS when SimplePeek available return m.newWrapperProxyPublisherRequest(ProxyID, pb1topics.TopicServiceRequestType_SimpleReceive, anyReq) } diff --git a/coherence/v1client.go b/coherence/v1client.go index d270332c..0df9d3ed 100644 --- a/coherence/v1client.go +++ b/coherence/v1client.go @@ -393,7 +393,7 @@ func processTopicEvent(m *streamManagerV1, resp *responseMessage) { case pb1topics.ResponseType_Event: var eventType = &pb1topics.NamedTopicEvent{} if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { - err = getUnmarshallError("ensure response", err) + err = getUnmarshallError("processTopicEvent response", err) m.session.debugConnection("cannot unmarshal topic response topic for topic %v: %v", topicName, err) } else { if eventType.Type == pb1topics.TopicEventType_TopicDestroyed { @@ -432,7 +432,7 @@ func processPublisherEvent(m *streamManagerV1, resp *responseMessage, publisherI case pb1topics.ResponseType_Event: var eventType = &pb1topics.PublisherEvent{} if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { - err = getUnmarshallError("ensure response", err) + err = getUnmarshallError("processPublisherEvent response", err) m.session.debugConnection("cannot unmarshal topic response topic for publisher %v: %v", publisherID, err) return } @@ -458,7 +458,7 @@ func processSubscriberEvent(m *streamManagerV1, resp *responseMessage, subscribe case pb1topics.ResponseType_Event: var eventType = &pb1topics.SubscriberEvent{} if err := resp.namedTopicResponse.Message.UnmarshalTo(eventType); err != nil { - err = getUnmarshallError("ensure response", err) + err = getUnmarshallError("processSubscriberEvent response", err) m.session.debugConnection("cannot unmarshal topic response topic for subscriber %v: %v", subscriberID, err) return }