grpc-dc 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ # @generated by grpc-dc codegen. DO NOT EDIT.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # Git log serde for the event bus: exports an event log to a
4
+ # git branch (one commit per event) and imports it back. The
5
+ # live broker never touches git; this is the durable serde
6
+ # layer beside it. Stdlib Open3 drives the git plumbing.
7
+ #
8
+ # Deterministic: the same events always produce the same
9
+ # commits, so re-exporting a log the branch already has is a
10
+ # no-op (each event's sequence number is the dedup key). The
11
+ # commit blob (event.pb) is authoritative; the message subject and
12
+ # trailers are a human index for `git log`.
13
+
14
+ require "open3"
15
+
16
+ require_relative "eventbus"
17
+ require_relative "events_pb"
18
+
19
+ module Grpcdc
20
+ module EventbusGit
21
+ AUTHOR_NAME = "grpc-dc eventbus"
22
+ AUTHOR_EMAIL = "eventbus@grpc-dc.local"
23
+ BLOB_PATH = "event.pb"
24
+ COMMIT_SUBJECT = "grpc-dc event"
25
+ TRAILER_SEQ = "Seq"
26
+ TRAILER_PUBLISHER = "Publisher"
27
+ TRAILER_TAG = "Tag"
28
+ ZERO_SHA = "0" * 40
29
+
30
+ EVENT_LOG_BRANCH = "grpc-dc-events"
31
+
32
+ def self.git(repo, args, input_bytes = nil, env_extra = nil)
33
+ out, _err, status = Open3.capture3(env_extra || {}, "git", "-C", repo, *args, stdin_data: input_bytes || "".b)
34
+ raise "git #{args.first} failed in #{repo}" unless status.success?
35
+
36
+ out
37
+ end
38
+
39
+ def self.git_text(repo, args, input_bytes = nil, env_extra = nil)
40
+ git(repo, args, input_bytes, env_extra).strip
41
+ end
42
+
43
+ # Create the log repo if needed (a plain `git init`).
44
+ def self.init_event_log_repo(repo)
45
+ git_text(repo, ["rev-parse", "--git-dir"])
46
+ rescue StandardError
47
+ out, _err, status = Open3.capture3("git", "init", "-q", repo)
48
+ raise "git init failed for #{repo}" unless status.success?
49
+
50
+ out
51
+ end
52
+
53
+ def self.branch_head(repo, branch)
54
+ git_text(repo, ["rev-parse", "--verify", "refs/heads/#{branch}"])
55
+ rescue StandardError
56
+ nil
57
+ end
58
+
59
+ # "grpc-dc event 42" plus Seq/Publisher/Tag trailers, so
60
+ # `git log` alone can filter the log without decoding blobs.
61
+ def self.commit_message(event)
62
+ lines = ["#{COMMIT_SUBJECT} #{event.seq}", "", "#{TRAILER_SEQ}: #{event.seq}"]
63
+ lines << "#{TRAILER_PUBLISHER}: #{event.publisher_id}" unless event.publisher_id.empty?
64
+ (event.tags || []).each { |tv| lines << "#{TRAILER_TAG}: #{Eventbus.tag_value_key(tv)}" }
65
+ lines.join("\n") + "\n"
66
+ end
67
+
68
+ def self.commit_env(event)
69
+ seconds = [0, event.timestamp_unix_ms / 1000].max
70
+ date = "#{seconds} +0000"
71
+ {
72
+ "GIT_AUTHOR_NAME" => AUTHOR_NAME,
73
+ "GIT_AUTHOR_EMAIL" => AUTHOR_EMAIL,
74
+ "GIT_COMMITTER_NAME" => AUTHOR_NAME,
75
+ "GIT_COMMITTER_EMAIL" => AUTHOR_EMAIL,
76
+ "GIT_AUTHOR_DATE" => date,
77
+ "GIT_COMMITTER_DATE" => date
78
+ }
79
+ end
80
+
81
+ # Import a branch: events in chain order (oldest first),
82
+ # optionally windowed by sequence number and narrowed by a
83
+ # tag filter (same containment semantics as Subscribe). A
84
+ # missing branch imports as an empty log.
85
+ def self.import_events(repo, branch = EVENT_LOG_BRANCH, from_seq: 0, to_seq: nil, filter_tags: nil)
86
+ return [] if branch_head(repo, branch).nil?
87
+
88
+ events = []
89
+ git_text(repo, ["rev-list", "--reverse", branch]).split("\n").each do |sha|
90
+ next if sha.empty?
91
+
92
+ data = git(repo, ["cat-file", "-p", "#{sha}:#{BLOB_PATH}"])
93
+ event = EventsPb::Event.new
94
+ event.unmarshal_dc(data)
95
+ next if event.seq <= from_seq
96
+ next if !to_seq.nil? && event.seq > to_seq
97
+ next if filter_tags && !Eventbus.tags_match(event.tags, filter_tags)
98
+
99
+ events << event
100
+ end
101
+ events
102
+ end
103
+
104
+ # Export events onto the branch: one commit per event, in
105
+ # sequence order, skipping sequence numbers the branch
106
+ # already has. Returns the new head sha (or the existing one
107
+ # when nothing was appended). The ref update is a
108
+ # compare-and-swap: a concurrent exporter fails loudly
109
+ # instead of forking the log; retrying is safe.
110
+ def self.export_events(repo, events, branch = EVENT_LOG_BRANCH)
111
+ init_event_log_repo(repo)
112
+ existing = {}
113
+ import_events(repo, branch).each { |e| existing[e.seq] = true }
114
+ head = branch_head(repo, branch)
115
+ before = head
116
+ events.sort_by(&:seq).each do |event|
117
+ next if existing.key?(event.seq)
118
+
119
+ blob = git_text(repo, ["hash-object", "-w", "--stdin"], event.marshal_dc)
120
+ tree = git_text(repo, ["mktree"], "100644 blob #{blob}\t#{BLOB_PATH}\n".encode(Encoding::UTF_8))
121
+ args = ["commit-tree", tree, "-m", commit_message(event)]
122
+ args += ["-p", head] unless head.nil?
123
+ head = git_text(repo, args, nil, commit_env(event))
124
+ existing[event.seq] = true
125
+ end
126
+ if !head.nil? && head != before
127
+ git_text(repo, ["update-ref", "refs/heads/#{branch}", head, before || ZERO_SHA])
128
+ end
129
+ head
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,232 @@
1
+ # Generated code: use without restriction (see GENERATED-CODE-LICENSE.md).
2
+ # @generated by protoc-gen-grpc-dc-ruby. DO NOT EDIT.
3
+ # source: events.proto
4
+
5
+ require_relative "events_pb"
6
+ require "grpcdc"
7
+
8
+ module EventsGrpcDc
9
+ # Client receiving stream: recv raises EOFError at end.
10
+ class RecvStream
11
+ include Enumerable
12
+
13
+ def initialize(stream, resp_cls)
14
+ @stream = stream
15
+ @resp_cls = resp_cls
16
+ end
17
+
18
+ def recv
19
+ @stream.recv(@resp_cls.new)
20
+ end
21
+
22
+ # Response initial metadata (after the first recv).
23
+ def headers
24
+ @stream.headers&.metadata
25
+ end
26
+
27
+ # Response trailing metadata (after the call ends).
28
+ def trailers
29
+ @stream.trailers
30
+ end
31
+
32
+ # Abandon the call early: RST so the server stops producing.
33
+ def cancel
34
+ @stream.cancel
35
+ end
36
+
37
+ def close
38
+ @stream.cancel
39
+ end
40
+
41
+ def each
42
+ return enum_for(:each) unless block_given?
43
+
44
+ begin
45
+ loop do
46
+ begin
47
+ yield recv
48
+ rescue EOFError
49
+ return
50
+ end
51
+ end
52
+ ensure
53
+ # Breaking out of the block abandons the call: RST so
54
+ # the server stops producing (no-op once terminated).
55
+ close
56
+ end
57
+ end
58
+ end
59
+
60
+ # Server-streaming sink passed to service implementations.
61
+ class SendStream
62
+ attr_reader :context
63
+
64
+ def initialize(ctx, sink)
65
+ @context = ctx
66
+ @sink = sink
67
+ end
68
+
69
+ def send(resp)
70
+ @sink.send(resp.marshal_dc)
71
+ end
72
+ end
73
+
74
+ # Client-streaming stream passed to service implementations.
75
+ class ClientStreamImpl
76
+ attr_reader :context, :resp
77
+
78
+ def initialize(ctx, reqs, req_cls)
79
+ @context = ctx
80
+ @reqs = reqs
81
+ @req_cls = req_cls
82
+ @resp = nil
83
+ end
84
+
85
+ def recv
86
+ @reqs.recv_into(@req_cls.new)
87
+ end
88
+
89
+ def send_and_close(resp)
90
+ @resp = resp
91
+ end
92
+ end
93
+
94
+ # Bidi stream passed to service implementations.
95
+ class BidiStream
96
+ attr_reader :context
97
+
98
+ def initialize(ctx, reqs, sink, req_cls)
99
+ @context = ctx
100
+ @reqs = reqs
101
+ @sink = sink
102
+ @req_cls = req_cls
103
+ end
104
+
105
+ def send(resp)
106
+ @sink.send(resp.marshal_dc)
107
+ end
108
+
109
+ def recv
110
+ @reqs.recv_into(@req_cls.new)
111
+ end
112
+ end
113
+
114
+ # Fiber-based client receiving stream: a producer Fiber
115
+ # yields each response; next_msg resumes it and raises
116
+ # StopIteration when the call completes. cancel aborts
117
+ # the underlying stream (RST).
118
+ class FiberRecvStream
119
+ include Enumerable
120
+
121
+ DONE = Object.new
122
+
123
+ def initialize(stream, resp_cls)
124
+ @stream = stream
125
+ @resp_cls = resp_cls
126
+ @fiber = Fiber.new do
127
+ begin
128
+ loop { Fiber.yield(@stream.recv(@resp_cls.new)) }
129
+ rescue EOFError
130
+ DONE
131
+ end
132
+ end
133
+ end
134
+
135
+ def next_msg
136
+ value = @fiber.resume
137
+ raise StopIteration if value.equal?(DONE)
138
+
139
+ value
140
+ end
141
+
142
+ # Response initial metadata (after the first recv).
143
+ def headers
144
+ @stream.headers&.metadata
145
+ end
146
+
147
+ # Response trailing metadata (after the call ends).
148
+ def trailers
149
+ @stream.trailers
150
+ end
151
+
152
+ def cancel
153
+ @stream.cancel
154
+ end
155
+
156
+ def close
157
+ @stream.cancel
158
+ end
159
+
160
+ def each
161
+ return enum_for(:each) unless block_given?
162
+
163
+ begin
164
+ loop do
165
+ begin
166
+ yield next_msg
167
+ rescue StopIteration
168
+ return
169
+ end
170
+ end
171
+ ensure
172
+ # Breaking out of the block abandons the call: RST so
173
+ # the server stops producing (no-op once terminated).
174
+ close
175
+ end
176
+ end
177
+ end
178
+
179
+ class EventBusClient
180
+ def initialize(channel)
181
+ @channel = channel
182
+ end
183
+
184
+ def Publish(req, metadata = nil, timeout: nil)
185
+ @channel.unary("/grpcdc.events.v1.EventBus/Publish", req, EventsPb::PublishReply.new, metadata, timeout: timeout)
186
+ end
187
+
188
+ def Subscribe(req, metadata = nil, timeout: nil)
189
+ stream = @channel.server_stream("/grpcdc.events.v1.EventBus/Subscribe", req, metadata, timeout: timeout)
190
+ RecvStream.new(stream, EventsPb::Event)
191
+ end
192
+
193
+ end
194
+
195
+ # Fiber client: wraps a Grpcdc::FiberChannel (a sync Channel is wrapped).
196
+ class EventBusClientFiber
197
+ def initialize(channel)
198
+ @channel = channel.is_a?(Grpcdc::FiberChannel) ? channel : Grpcdc::FiberChannel.new(channel)
199
+ end
200
+
201
+ def Publish(req, metadata = nil, timeout: nil)
202
+ @channel.unary("/grpcdc.events.v1.EventBus/Publish", req, EventsPb::PublishReply.new, metadata, timeout: timeout)
203
+ end
204
+
205
+ def Subscribe(req, metadata = nil, timeout: nil)
206
+ stream = @channel.server_stream("/grpcdc.events.v1.EventBus/Subscribe", req, metadata, timeout: timeout)
207
+ FiberRecvStream.new(stream, EventsPb::Event)
208
+ end
209
+
210
+ end
211
+
212
+ def self.register_event_bus_server(server, impl)
213
+ publish_handler = lambda do |ctx, reqs, sink|
214
+ req = reqs.recv_into(EventsPb::PublishRequest.new)
215
+ resp = impl.Publish(ctx, req)
216
+ sink.send(resp.marshal_dc)
217
+ end
218
+
219
+ subscribe_handler = lambda do |ctx, reqs, sink|
220
+ req = reqs.recv_into(EventsPb::SubscribeRequest.new)
221
+ impl.Subscribe(req, SendStream.new(ctx, sink))
222
+ end
223
+
224
+ server.register_service(Grpcdc::ServiceDesc.new(
225
+ "grpcdc.events.v1.EventBus",
226
+ [
227
+ Grpcdc::Method.new("Publish", "/grpcdc.events.v1.EventBus/Publish", Grpcdc::KIND_UNARY, publish_handler),
228
+ Grpcdc::Method.new("Subscribe", "/grpcdc.events.v1.EventBus/Subscribe", Grpcdc::KIND_SERVER_STREAMING, subscribe_handler),
229
+ ]
230
+ ))
231
+ end
232
+ end