tep 0.11.4 → 0.11.6
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/README.md +34 -30
- data/bin/tep +49 -4
- data/examples/pg_hello.rb +12 -1
- data/lib/tep/broadcast.rb +18 -80
- data/lib/tep/net.rb +8 -3
- data/lib/tep/pg.rb +444 -5
- data/lib/tep/presence.rb +22 -318
- data/lib/tep/proxy.rb +2 -2
- data/lib/tep/shell.rb +17 -6
- 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: 33b663dcc38571d71f8a729f7ed1f8da6ea8a89e1eb6bd49619beac977da2727
|
|
4
|
+
data.tar.gz: af7b47e7e3440372325744d1274e70e41216db757ac224ca2364c1a422b5064a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed5ce816c591c5464d624e3094798d3ce8c8c03c7128e3881541cf874a0735e62f468d7d4f781e793e6ae02f2bcb642119fdc49157394acb69cb027a014d9b6e
|
|
7
|
+
data.tar.gz: 484a1cdae65a65cbe5da98d8c2aa22add1b4a562e35ab7cfb38f9137856bcad77ad773b07d191fb425a5e6307fe18a71856d47ad3766e8a03191622314ba2989
|
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/README.md
CHANGED
|
@@ -4,37 +4,41 @@
|
|
|
4
4
|
|
|
5
5
|
# Tep
|
|
6
6
|
|
|
7
|
-
A Sinatra-flavoured web framework that compiles to a native binary
|
|
8
|
-
via [Spinel][spinel].
|
|
7
|
+
A Sinatra-flavoured web framework that compiles to a **native binary**
|
|
8
|
+
via [Spinel][spinel]. You write a Sinatra-style `app.rb`:
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
```ruby
|
|
11
|
+
require 'sinatra'
|
|
12
|
+
|
|
13
|
+
get '/hi/:name' do
|
|
14
|
+
"hi, " + params[:name] + "!"
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
…and get `./app` — a single static executable, ~80 KB, **no Ruby
|
|
19
|
+
runtime**, doing ~150k req/s. Beyond routing / sessions / templates, the
|
|
20
|
+
[batteries ↓](#whats-in-the-box) cover auth (bearer / cookie / OAuth2),
|
|
21
|
+
SQLite, opt-in PostgreSQL, WebSockets, Broadcast + Presence + LiveView,
|
|
22
|
+
an MCP tool catalog, TLS, a pooled HTTP client, and an OpenAI-compatible
|
|
23
|
+
LLM client + server.
|
|
24
|
+
|
|
25
|
+
> **Current release:** [v0.11.6](https://github.com/OriPekelman/tep/releases/tag/v0.11.6)
|
|
26
|
+
> on [RubyGems](https://rubygems.org/gems/tep) — `gem install tep`.
|
|
14
27
|
> Pre-alpha; API still in motion.
|
|
15
28
|
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
>
|
|
29
|
-
> compiled by Spinel. Toy needs an HTTP/MCP layer for serving
|
|
30
|
-
> models, exposing training tools to agents (Claude Code et al.),
|
|
31
|
-
> streaming inference results, and wiring presence into
|
|
32
|
-
> collaborative training sessions. Tep is that layer. Every
|
|
33
|
-
> battery in Tep is shaped by what Toy actually needs to ship.
|
|
34
|
-
>
|
|
35
|
-
> Tep happens to be useful as a general web framework too — fast,
|
|
36
|
-
> single-binary, Sinatra-shaped — but the design choices are made
|
|
37
|
-
> through these two lenses.
|
|
29
|
+
<details>
|
|
30
|
+
<summary><b>Why Tep exists</b> — it's a deliberate Spinel torture test + toy's serving layer</summary>
|
|
31
|
+
|
|
32
|
+
Tep is the largest pure-Ruby application Spinel compiles end-to-end:
|
|
33
|
+
every Sinatra idiom and battery doubles as a torture test for the AOT
|
|
34
|
+
compiler's codegen + analyzer, and bugs found here get reduced to
|
|
35
|
+
minimal repros and land upstream as
|
|
36
|
+
[matz/spinel](https://github.com/matz/spinel) PRs. It's also the
|
|
37
|
+
HTTP / MCP / serving layer for its sibling [toy][toy] — a pure-Ruby ML
|
|
38
|
+
framework Spinel compiles — so every battery is shaped by what toy
|
|
39
|
+
needs to ship. Tep is a genuinely useful general web framework too, but
|
|
40
|
+
the design choices are made through those two lenses.
|
|
41
|
+
</details>
|
|
38
42
|
|
|
39
43
|
[toy]: https://github.com/OriPekelman/toy
|
|
40
44
|
|
|
@@ -107,7 +111,7 @@ gem install. Recommended Ruby manager:
|
|
|
107
111
|
[`rv`](https://github.com/spinel-coop/rv) — fast version+gem
|
|
108
112
|
manager from the Spinel Cooperative (separate project from the
|
|
109
113
|
matz/spinel AOT compiler Tep compiles through; same Ruby
|
|
110
|
-
neighbourhood). `.ruby-version` in this repo pins
|
|
114
|
+
neighbourhood). `.ruby-version` in this repo pins 4.0.0; `rv
|
|
111
115
|
shell` makes `rv run rake test` just work. Build deps on Linux:
|
|
112
116
|
`build-essential`, `libsqlite3-dev`. macOS: Xcode CLI tools.
|
|
113
117
|
|
|
@@ -174,7 +178,7 @@ through Spinel.
|
|
|
174
178
|
| `Tep::WebSocket` | RFC 6455 server-side WebSocket. `websocket '/chat' do \|ws\| ... end` DSL lowers to Frame + Handshake + Driver + Connection. Requires `set :scheduler, :scheduled`. |
|
|
175
179
|
| `Tep::Parallel` | grosser/parallel-shaped fork fan-out. |
|
|
176
180
|
| `Tep::Job` | sidekiq-shaped queue over SQLite. |
|
|
177
|
-
| `PG` | ruby-pg-shape libpq client
|
|
181
|
+
| `PG` | **Opt-in** ruby-pg-shape libpq client — `require "tep/pg"` (so non-PG apps don't link libpq). `PG::Connection`, `PG::Result`, `PG::Error`; surface mirrors the `pg` gem (`exec` / `exec_params` / `escape_*` / `fields` / `values` / `getvalue` / `sql_state`). Designed so an eventual ActiveRecord-on-spinel port reuses the existing AR adapter with minimal divergence — see `docs/PG-BATTERY.md`. |
|
|
178
182
|
| `Tep::Auth` | Principal+delegate identity (`Tep::Identity` / `Tep::AgentDelegation`) + provider chain. Three providers shipped: `Tep::AuthBearerToken` (JWT-HS256), `Tep::AuthSessionCookie` (signed cookie), `Tep::AuthOAuth2` (authorization-code grant issuance for bots/agents). Same `req.identity` surface regardless of provider; agents are first-class (`identity.agent?`, `identity.acting_via.agent_id`, capability subsetting). |
|
|
179
183
|
| `Tep::Broadcast` | In-process pub-sub + cross-worker via PG LISTEN/NOTIFY. Subscribe an fd to a topic (`subscribe` raw, `subscribe_ws` WS-frame-wrapped); publish writes to every matching subscriber. The seam Presence and LiveView build on. |
|
|
180
184
|
| `Tep::Presence` | Topic-keyed who's-here registry, agent-aware. `Tep::Presence.track(req, topic, fd)` records a (principal, session, topic) tuple with a 3-state structured status (`:available | :busy | :blocked` + free-text note + expiry). Diffs broadcast on join/leave/status; PG-mirror for cross-worker `list_global` snapshots. |
|
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
|
|
@@ -1586,7 +1610,14 @@ def rewrite_block(src, force_string: true)
|
|
|
1586
1610
|
# are handled separately so they can contain commas or spaces.
|
|
1587
1611
|
pairs = $2.scan(/(\w+):\s*("[^"]*"|'[^']*'|[^,}]+?)\s*(?=,|\z)/)
|
|
1588
1612
|
setters = pairs.map { |k, v| %{__l["#{k}"] = (#{v.strip}).to_s} }.join("; ")
|
|
1589
|
-
|
|
1613
|
+
# Build the locals hash INSIDE the first argument (a sequence
|
|
1614
|
+
# expression that returns __l), rather than `(__l = ...; render(__l))`
|
|
1615
|
+
# with __l a bare arg. In the latter shape spinel (recent master)
|
|
1616
|
+
# hoists the `render`'s arg temp -- `_t = __l` -- above the in-sequence
|
|
1617
|
+
# `__l = str_hash()` assignment, so render receives the pre-assignment
|
|
1618
|
+
# (empty) hash and every interpolated value blanks (matz/spinel#1478).
|
|
1619
|
+
# Keeping the assignment inside the arg evaluation defeats the hoist.
|
|
1620
|
+
"tep_view_#{view}((__l = Tep.str_hash; #{setters}; __l), req.ivars)"
|
|
1590
1621
|
end
|
|
1591
1622
|
s = s.gsub(/(?<![\w.])erb\s+:(\w+)/, 'tep_view_\1(Tep.str_hash, req.ivars)')
|
|
1592
1623
|
|
|
@@ -1595,7 +1626,8 @@ def rewrite_block(src, force_string: true)
|
|
|
1595
1626
|
view = $1
|
|
1596
1627
|
pairs = $2.scan(/(\w+):\s*("[^"]*"|'[^']*'|[^,}]+?)\s*(?=,|\z)/)
|
|
1597
1628
|
setters = pairs.map { |k, v| %{__l["#{k}"] = (#{v.strip}).to_s} }.join("; ")
|
|
1598
|
-
|
|
1629
|
+
# Same arg-hoist avoidance as the erb form above (matz/spinel#1478).
|
|
1630
|
+
"tep_mustache_#{view}((__l = Tep.str_hash; #{setters}; __l), req.ivars)"
|
|
1599
1631
|
end
|
|
1600
1632
|
s = s.gsub(/(?<![\w.])mustache\s+:(\w+)/, 'tep_mustache_\1(Tep.str_hash, req.ivars)')
|
|
1601
1633
|
|
|
@@ -1947,7 +1979,11 @@ def tep_ext_subs
|
|
|
1947
1979
|
else
|
|
1948
1980
|
cflags = ENV.fetch(env_var, "")
|
|
1949
1981
|
libs_var = env_var.sub(/_CFLAGS\z/, "_LIBS")
|
|
1950
|
-
|
|
1982
|
+
# Prefer an explicit pkg_config_fallback (e.g. openssl -> "-lssl
|
|
1983
|
+
# -lcrypto"); the bare "-l<pkg_config>" derivation is only right when
|
|
1984
|
+
# the lib name matches the .pc name (libpq -> -lpq), not for openssl.
|
|
1985
|
+
default_libs = entry["pkg_config_fallback"] ||
|
|
1986
|
+
(entry["pkg_config"] ? "-l" + entry["pkg_config"].sub(/\Alib/, "") : "")
|
|
1951
1987
|
libs = ENV.fetch(libs_var, default_libs)
|
|
1952
1988
|
subs[placeholder] = "#{cflags} #{libs}".strip
|
|
1953
1989
|
end
|
|
@@ -1979,7 +2015,16 @@ def resolve_ext_o(entry, entries, env_var)
|
|
|
1979
2015
|
entries.each do |sib|
|
|
1980
2016
|
next unless sib["name"] == entry["name"] && sib["pkg_config"]
|
|
1981
2017
|
pc = `pkg-config --cflags #{sib["pkg_config"]} 2>/dev/null`
|
|
1982
|
-
$?.success?
|
|
2018
|
+
if $?.success?
|
|
2019
|
+
cflags.concat(pc.split)
|
|
2020
|
+
elsif sib["pkg_config_fallback"]
|
|
2021
|
+
# pkg-config can't find a .pc (e.g. openssl on a host without it):
|
|
2022
|
+
# assume the headers are on the default include path -- the
|
|
2023
|
+
# historical behavior before this sibling existed -- and let the
|
|
2024
|
+
# link use the fallback libs. Don't fail an otherwise-fine compile.
|
|
2025
|
+
else
|
|
2026
|
+
missing = sib["pkg_config"]
|
|
2027
|
+
end
|
|
1983
2028
|
end
|
|
1984
2029
|
if missing
|
|
1985
2030
|
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,17 @@ 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 -- still open at SPINEL_PIN (re-checked at the
|
|
72
|
+
# ad2b71ad re-pin: `e.is_a?(PG::Error)` here is rejected as
|
|
73
|
+
# `unsupported call: is_a? recv=LocalVariableRead argc=1`).
|
|
74
|
+
# `e` is the rescued exception, typed PG::UndefinedTable -- a
|
|
75
|
+
# whole-program is_a? against the namespaced ancestor PG::Error
|
|
76
|
+
# isn't lowered yet. Minimal `rescue Sub => e; e.is_a?(Super)`
|
|
77
|
+
# compiles fine; only the full program trips it. Since `e` is
|
|
78
|
+
# always a PG::Error subclass here, hardcode "yes". Restore
|
|
79
|
+
# (e.is_a?(PG::Error) ? "yes" : "no")
|
|
80
|
+
# once is_a?-on-rescued-namespaced-ancestor lowers. See tep#196.
|
|
81
|
+
"is PG::Error? " + "yes" + "\n" +
|
|
71
82
|
"message: " + e.message
|
|
72
83
|
end
|
|
73
84
|
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
|
|