tep 0.11.4 → 0.11.5
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.
- checksums.yaml +4 -4
- data/Makefile +11 -1
- data/bin/tep +39 -2
- data/examples/pg_hello.rb +11 -1
- data/lib/tep/broadcast.rb +18 -80
- data/lib/tep/net.rb +8 -3
- data/lib/tep/pg.rb +468 -14
- data/lib/tep/presence.rb +22 -318
- data/lib/tep/version.rb +1 -1
- data/lib/tep.rb +26 -107
- data/spinel-ext.json +6 -0
- data/test/test_broadcast_pg.rb +1 -0
- data/test/test_pg.rb +1 -0
- data/test/test_presence_pg.rb +1 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0f6e0ace2299c85ad62734b3af3b5b5a704994d2c0769f3151f1741b2c7ec26
|
|
4
|
+
data.tar.gz: 0d57c798029526125f7698d20c9f6419c557381b0d99ebb55be2ca35a8e819d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5bc56857336297807d32345e8957dfafb714c6f15445da6b69a833b2d41090a4c3a8918ca89f5ec02bdbb593db1ed79996fb3ef5a271aec768358fe26604e67
|
|
7
|
+
data.tar.gz: 8fadaa359cdee7cd715e61e89c83ce29e82d471cfb2f00489c365f98078f7d50368aba77542fbe5e3fa6873898803a499c77913fac2dd32e01914cf3bdb7d5d0
|
data/Makefile
CHANGED
|
@@ -34,6 +34,16 @@ TEP_PG_LIBS ?= $(shell \
|
|
|
34
34
|
(pg_config --libdir 2>/dev/null | sed -e 's|^|-L|' ; echo "-lpq") | tr '\n' ' ')
|
|
35
35
|
export TEP_PG_CFLAGS TEP_PG_LIBS
|
|
36
36
|
|
|
37
|
+
# OpenSSL cflags / libs for sphttp.c's outbound-TLS include (#148). On Linux
|
|
38
|
+
# the headers/libs are on the default search path so this is usually empty;
|
|
39
|
+
# on macOS Homebrew keeps openssl@3 keg-only (/opt/homebrew/opt/openssl@3),
|
|
40
|
+
# off the default path, so pkg-config supplies the -I (compile) and -L/-lssl
|
|
41
|
+
# /-lcrypto (link). If pkg-config can't find openssl, fall back to the bare
|
|
42
|
+
# libs and let the include path come from the system default.
|
|
43
|
+
TEP_SPHTTP_CFLAGS ?= $(shell pkg-config --cflags openssl 2>/dev/null)
|
|
44
|
+
TEP_SPHTTP_LIBS ?= $(shell pkg-config --libs openssl 2>/dev/null || echo "-lssl -lcrypto")
|
|
45
|
+
export TEP_SPHTTP_CFLAGS TEP_SPHTTP_LIBS
|
|
46
|
+
|
|
37
47
|
.PHONY: all clean helper hello sinatra_style bench bench-tep bench-sinatra demo test test-parallel spinel-fresh test-pg vendor-examples vendor-spinelkit doctor
|
|
38
48
|
|
|
39
49
|
# Re-sync the vendored SpinelKit lib (lib/spinel_kit/, sig/spinel_kit/) from the
|
|
@@ -73,7 +83,7 @@ all: spinel-fresh helper hello sinatra_style bench
|
|
|
73
83
|
helper: spinel-fresh $(LIB_DIR)/sphttp.o $(LIB_DIR)/tep_sqlite.o $(LIB_DIR)/tep_pg.o
|
|
74
84
|
|
|
75
85
|
$(LIB_DIR)/sphttp.o: $(LIB_DIR)/sphttp.c
|
|
76
|
-
cc -O2 -c $< -o $@
|
|
86
|
+
cc -O2 -c $(TEP_SPHTTP_CFLAGS) $< -o $@
|
|
77
87
|
|
|
78
88
|
$(LIB_DIR)/tep_sqlite.o: $(LIB_DIR)/tep_sqlite.c
|
|
79
89
|
cc -O2 -c $< -o $@
|
data/bin/tep
CHANGED
|
@@ -1366,6 +1366,30 @@ def handle_top_call(node, routes, websockets, mcp_tools, mcp_resources, filters,
|
|
|
1366
1366
|
|
|
1367
1367
|
case name
|
|
1368
1368
|
when "require", "require_relative"
|
|
1369
|
+
# tep/pg is OPT-IN (#216): it is NOT part of the wholesale-inlined
|
|
1370
|
+
# core (lib/tep.rb no longer require_relative's it). An app, test, or
|
|
1371
|
+
# example that needs PostgreSQL says `require "tep/pg"` (or, by path,
|
|
1372
|
+
# `require_relative ".../lib/tep/pg"`); we splice lib/tep/pg.rb in
|
|
1373
|
+
# once here. A non-PG app never names it, so the whole pg closure
|
|
1374
|
+
# (the 74 tep_pg_* FFI symbols + libpq) DCEs away. This must run
|
|
1375
|
+
# BEFORE the is_tep_lib no-op below, which would otherwise swallow
|
|
1376
|
+
# any "tep/..." spelling as already-inlined.
|
|
1377
|
+
pg_arg = node.arguments&.arguments&.first
|
|
1378
|
+
if pg_arg.is_a?(Prism::StringNode) &&
|
|
1379
|
+
(pg_arg.unescaped == "tep/pg" || pg_arg.unescaped.end_with?("/tep/pg"))
|
|
1380
|
+
pgpath = File.join(LIB_DIR, "tep", "pg.rb")
|
|
1381
|
+
if inlined_seen && File.file?(pgpath) && !inlined_seen[pgpath]
|
|
1382
|
+
pgtext = inline_require_relative_tree(pgpath, inlined_seen, warnings)
|
|
1383
|
+
# Resolve pg.rb's @TEP_PG_O@ / @TEP_PG_CFLAGS@ placeholders the
|
|
1384
|
+
# same way inlined_tep_library does for the wholesale core -- the
|
|
1385
|
+
# opt-in splice bypasses that pass, so without this the literal
|
|
1386
|
+
# placeholders reach the linker ("ld: cannot find @TEP_PG_O@").
|
|
1387
|
+
tep_ext_subs.each { |placeholder, value| pgtext = pgtext.gsub(placeholder, value) }
|
|
1388
|
+
passthrough << "# --- inlined opt-in require #{pg_arg.unescaped.inspect} (pg.rb) ---"
|
|
1389
|
+
passthrough << pgtext
|
|
1390
|
+
end
|
|
1391
|
+
return
|
|
1392
|
+
end
|
|
1369
1393
|
# tep's own library is inlined wholesale (see inlined_tep_library), so
|
|
1370
1394
|
# `require_relative "tep"` / "tep/..." is a no-op. A plain `require
|
|
1371
1395
|
# "gem"` (by name) is dropped -- spinel has no gem load path. But an
|
|
@@ -1947,7 +1971,11 @@ def tep_ext_subs
|
|
|
1947
1971
|
else
|
|
1948
1972
|
cflags = ENV.fetch(env_var, "")
|
|
1949
1973
|
libs_var = env_var.sub(/_CFLAGS\z/, "_LIBS")
|
|
1950
|
-
|
|
1974
|
+
# Prefer an explicit pkg_config_fallback (e.g. openssl -> "-lssl
|
|
1975
|
+
# -lcrypto"); the bare "-l<pkg_config>" derivation is only right when
|
|
1976
|
+
# the lib name matches the .pc name (libpq -> -lpq), not for openssl.
|
|
1977
|
+
default_libs = entry["pkg_config_fallback"] ||
|
|
1978
|
+
(entry["pkg_config"] ? "-l" + entry["pkg_config"].sub(/\Alib/, "") : "")
|
|
1951
1979
|
libs = ENV.fetch(libs_var, default_libs)
|
|
1952
1980
|
subs[placeholder] = "#{cflags} #{libs}".strip
|
|
1953
1981
|
end
|
|
@@ -1979,7 +2007,16 @@ def resolve_ext_o(entry, entries, env_var)
|
|
|
1979
2007
|
entries.each do |sib|
|
|
1980
2008
|
next unless sib["name"] == entry["name"] && sib["pkg_config"]
|
|
1981
2009
|
pc = `pkg-config --cflags #{sib["pkg_config"]} 2>/dev/null`
|
|
1982
|
-
$?.success?
|
|
2010
|
+
if $?.success?
|
|
2011
|
+
cflags.concat(pc.split)
|
|
2012
|
+
elsif sib["pkg_config_fallback"]
|
|
2013
|
+
# pkg-config can't find a .pc (e.g. openssl on a host without it):
|
|
2014
|
+
# assume the headers are on the default include path -- the
|
|
2015
|
+
# historical behavior before this sibling existed -- and let the
|
|
2016
|
+
# link use the fallback libs. Don't fail an otherwise-fine compile.
|
|
2017
|
+
else
|
|
2018
|
+
missing = sib["pkg_config"]
|
|
2019
|
+
end
|
|
1983
2020
|
end
|
|
1984
2021
|
if missing
|
|
1985
2022
|
return "" if entry["optional"]
|
data/examples/pg_hello.rb
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# today (matz/spinel#627). Once that lands, this example collapses
|
|
17
17
|
# to the AR-shape `rescue PG::Error => e`.
|
|
18
18
|
require_relative "../lib/tep"
|
|
19
|
+
require_relative "../lib/tep/pg" # opt-in PG backend (#216)
|
|
19
20
|
|
|
20
21
|
PG_URL = ENV["PG_URL"] != nil && ENV["PG_URL"].length > 0 ? ENV["PG_URL"] : "postgresql:///postgres"
|
|
21
22
|
|
|
@@ -67,7 +68,16 @@ get '/error' do
|
|
|
67
68
|
out = "rescued PG::UndefinedTable\n" +
|
|
68
69
|
"sqlstate: " + c.last_sqlstate + "\n" +
|
|
69
70
|
"is undefined-table? " + (c.last_sqlstate == "42P01" ? "yes" : "no") + "\n" +
|
|
70
|
-
|
|
71
|
+
# WORKAROUND -- REMOVE WHEN UPSTREAM LANDS: `e.is_a?(PG::Error)`
|
|
72
|
+
# miscompiles at spinel master (whole-program is_a? on a deep
|
|
73
|
+
# exception subclass vs an ancestor -- e here is rescued as
|
|
74
|
+
# PG::UndefinedTable, a PG::Error subclass, so this is always
|
|
75
|
+
# "yes"). Minimal `rescue Sub => e; e.is_a?(Super)` repros
|
|
76
|
+
# compile fine; it only trips in the full program. Hardcoded
|
|
77
|
+
# to "yes" to keep the example building for the re-pin. See
|
|
78
|
+
# matz/spinel#1434, tep#196. Original:
|
|
79
|
+
# (e.is_a?(PG::Error) ? "yes" : "no")
|
|
80
|
+
"is PG::Error? " + "yes" + "\n" +
|
|
71
81
|
"message: " + e.message
|
|
72
82
|
end
|
|
73
83
|
c.close
|
data/lib/tep/broadcast.rb
CHANGED
|
@@ -114,14 +114,21 @@ module Tep
|
|
|
114
114
|
# remote deliveries are best-effort and not counted here.
|
|
115
115
|
def self.publish(topic, payload)
|
|
116
116
|
matched = Tep::Broadcast.publish_local_only(topic, payload)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
# Cross-worker fan-out is an OPT-IN hook (#216): the core build
|
|
118
|
+
# has no PG reference, so a non-PG app DCEs the entire libpq
|
|
119
|
+
# closure. `require "tep/pg"` redefines cross_worker_notify with
|
|
120
|
+
# the real broadcast_pg_conn.notify (last-definition-wins).
|
|
121
|
+
Tep::Broadcast.cross_worker_notify(topic, payload)
|
|
122
122
|
matched
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
+
# No-op cross-worker hook. Overridden by lib/tep/pg.rb when the PG
|
|
126
|
+
# backend is loaded. Keeping the PG NOTIFY out of core is what lets
|
|
127
|
+
# `Tep::Broadcast.publish` compile without pulling tep_pg_*/libpq.
|
|
128
|
+
def self.cross_worker_notify(topic, payload)
|
|
129
|
+
0
|
|
130
|
+
end
|
|
131
|
+
|
|
125
132
|
# Total subscription count across all topics. Useful for
|
|
126
133
|
# diagnostics and the v1 test surface.
|
|
127
134
|
def self.subscriber_count
|
|
@@ -157,81 +164,12 @@ module Tep
|
|
|
157
164
|
|
|
158
165
|
# ---- PG backend (cross-worker pub/sub) ----
|
|
159
166
|
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# safe SQL identifier (e.g. "tep_broadcast") since it lands
|
|
167
|
-
# inside a LISTEN / NOTIFY command unescaped.
|
|
168
|
-
#
|
|
169
|
-
# Returns 0 on success, -1 on connection or LISTEN failure.
|
|
170
|
-
def self.enable_pg_backend(conninfo, channel)
|
|
171
|
-
conn = PG::Connection.new(conninfo)
|
|
172
|
-
if conn.pgh < 0
|
|
173
|
-
return -1
|
|
174
|
-
end
|
|
175
|
-
if conn.listen(channel) < 0
|
|
176
|
-
return -1
|
|
177
|
-
end
|
|
178
|
-
Tep::APP.set_broadcast_pg_conn(conn)
|
|
179
|
-
Tep::APP.set_broadcast_pg_channel(channel)
|
|
180
|
-
Tep::APP.set_broadcast_pg_enabled(1)
|
|
181
|
-
0
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def self.disable_pg_backend
|
|
185
|
-
if Tep::APP.broadcast_pg_enabled == 0
|
|
186
|
-
return 0
|
|
187
|
-
end
|
|
188
|
-
Tep::APP.broadcast_pg_conn.unlisten(Tep::APP.broadcast_pg_channel)
|
|
189
|
-
Tep::APP.broadcast_pg_conn.finish
|
|
190
|
-
Tep::APP.set_broadcast_pg_enabled(0)
|
|
191
|
-
0
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Process one notification from the PG channel: parse the wire
|
|
195
|
-
# format, dispatch to local subscribers as if `publish` had
|
|
196
|
-
# been called locally (but WITHOUT re-NOTIFYing -- that would
|
|
197
|
-
# loop). Returns 1 if a notification was processed, 0 on
|
|
198
|
-
# timeout, -1 on connection error or unenabled backend.
|
|
199
|
-
def self.poll_pg_once(timeout_ms)
|
|
200
|
-
if Tep::APP.broadcast_pg_enabled == 0
|
|
201
|
-
return -1
|
|
202
|
-
end
|
|
203
|
-
r = Tep::APP.broadcast_pg_conn.poll_notification(timeout_ms)
|
|
204
|
-
if r != 1
|
|
205
|
-
return r
|
|
206
|
-
end
|
|
207
|
-
wire = Tep::APP.broadcast_pg_conn.last_notify_payload
|
|
208
|
-
Tep::Broadcast.deliver_wire_local(wire)
|
|
209
|
-
1
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Wire format: "<topic_byte_length>:<topic><payload>".
|
|
213
|
-
# Length-prefixed so topics and payloads with arbitrary chars
|
|
214
|
-
# (commas, colons, embedded quotes, newlines) round-trip
|
|
215
|
-
# unambiguously. Encoded by `publish` when the PG backend is
|
|
216
|
-
# enabled; decoded by `deliver_wire_local`.
|
|
217
|
-
def self.encode_wire(topic, payload)
|
|
218
|
-
topic.length.to_s + ":" + topic + payload
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def self.deliver_wire_local(wire)
|
|
222
|
-
colon = Tep.str_find(wire, ":", 0)
|
|
223
|
-
if colon <= 0
|
|
224
|
-
return -1
|
|
225
|
-
end
|
|
226
|
-
len_str = wire[0, colon]
|
|
227
|
-
tlen = len_str.to_i
|
|
228
|
-
if tlen < 0 || colon + 1 + tlen > wire.length
|
|
229
|
-
return -1
|
|
230
|
-
end
|
|
231
|
-
topic = wire[colon + 1, tlen]
|
|
232
|
-
payload = wire[colon + 1 + tlen, wire.length - colon - 1 - tlen]
|
|
233
|
-
Tep::Broadcast.publish_local_only(topic, payload)
|
|
234
|
-
end
|
|
167
|
+
# The PG LISTEN/NOTIFY backend is OPT-IN (#216). enable_pg_backend /
|
|
168
|
+
# disable_pg_backend / poll_pg_once, plus the wire encode/decode
|
|
169
|
+
# helpers (encode_wire / deliver_wire_local) and the
|
|
170
|
+
# cross_worker_notify override, live in lib/tep/pg.rb and only
|
|
171
|
+
# compile into apps that `require "tep/pg"`. Core Broadcast carries
|
|
172
|
+
# no PG reference so a non-PG app DCEs the libpq closure entirely.
|
|
235
173
|
|
|
236
174
|
# Same fan-out as #publish but skips the PG NOTIFY step. Used
|
|
237
175
|
# internally by poll_pg_once when delivering a cross-worker
|
data/lib/tep/net.rb
CHANGED
|
@@ -12,9 +12,14 @@ module Sock
|
|
|
12
12
|
# libssl/libcrypto. Linked for every app (like sqlite3 elsewhere);
|
|
13
13
|
# the plaintext path never calls into it, so apps that make no HTTPS
|
|
14
14
|
# requests pay only the link cost, not runtime. See tep#148.
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
#
|
|
16
|
+
# OpenSSL include/lib paths come via @TEP_SPHTTP_CFLAGS@ (the
|
|
17
|
+
# pkg_config sibling in spinel-ext.json -- `pkg-config openssl`,
|
|
18
|
+
# fallback `-lssl -lcrypto`), mirroring @TEP_PG_CFLAGS@. On Linux it's
|
|
19
|
+
# often just the libs (headers on the default path); on macOS/Homebrew
|
|
20
|
+
# it supplies the keg-only -I/-L too, so sphttp.c compiles + the
|
|
21
|
+
# ffi_lib "ssl"/"crypto" below resolve. See tep#208.
|
|
22
|
+
ffi_cflags "@TEP_SPHTTP_CFLAGS@"
|
|
18
23
|
ffi_lib "ssl"
|
|
19
24
|
ffi_lib "crypto"
|
|
20
25
|
|