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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 440ba4bc444318eb021fa61653935297e7fee6876f7ce09e06bd45e091f5a14a
4
- data.tar.gz: 3babc819f652798441f6f9ecc0f088afb398dae0e977186e9bb3c3e9fc455562
3
+ metadata.gz: a0f6e0ace2299c85ad62734b3af3b5b5a704994d2c0769f3151f1741b2c7ec26
4
+ data.tar.gz: 0d57c798029526125f7698d20c9f6419c557381b0d99ebb55be2ca35a8e819d4
5
5
  SHA512:
6
- metadata.gz: 514652e89448eef94073b1680228ccbc876f53df63712f580f1c3c8e8b50b0852d0630f456ca26df93844456d7b124ee1681c794e997d41dffd8299e8cb3058d
7
- data.tar.gz: be8785d792ad05e0dcde98a04aba8426b967230a42797e47d1536ad67057a7f78bf03f23ded9194c0abd10d3e33c8d0b6023964e7735020d2ca002fb0afc90d1
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
- default_libs = entry["pkg_config"] ? "-l" + entry["pkg_config"].sub(/\Alib/, "") : ""
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? ? cflags.concat(pc.split) : (missing = sib["pkg_config"])
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
- "is PG::Error? " + (e.is_a?(PG::Error) ? "yes" : "no") + "\n" +
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
- if Tep::APP.broadcast_pg_enabled != 0
118
- wire = Tep::Broadcast.encode_wire(topic, payload)
119
- Tep::APP.broadcast_pg_conn.notify(
120
- Tep::APP.broadcast_pg_channel, wire)
121
- end
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
- # Opens a dedicated PG connection and issues `LISTEN <channel>`.
161
- # Subsequent publishes NOTIFY this channel too -- other workers
162
- # subscribed to the same channel can receive the message via
163
- # poll_pg_once.
164
- #
165
- # `conninfo` is the libpq connect string. `channel` must be a
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
- # (When OpenSSL is off the default path -- macOS/Homebrew -- the build
16
- # finds it via CPATH/LIBRARY_PATH in the environment, not a cflag
17
- # here; spinel's ffi_cflags rejects an empty-string placeholder.)
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