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
data/lib/tep/presence.rb
CHANGED
|
@@ -265,325 +265,29 @@ module Tep
|
|
|
265
265
|
swept
|
|
266
266
|
end
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
#
|
|
285
|
-
# Returns 0 on success, -1 on connect / schema failure.
|
|
286
|
-
def self.enable_pg_mirror(conninfo)
|
|
287
|
-
conn = PG::Connection.new(conninfo)
|
|
288
|
-
if conn.pgh < 0
|
|
289
|
-
return -1
|
|
290
|
-
end
|
|
291
|
-
# exec raises PG::Error on failure now; degrade gracefully
|
|
292
|
-
# (close + return -1) rather than letting it escape the worker.
|
|
293
|
-
begin
|
|
294
|
-
r = conn.exec(Tep::Presence.schema_sql)
|
|
295
|
-
r.clear
|
|
296
|
-
# Heartbeat table for the prune-stale-workers path (#47).
|
|
297
|
-
r = conn.exec(Tep::Presence.worker_schema_sql)
|
|
298
|
-
r.clear
|
|
299
|
-
rescue PG::Error
|
|
300
|
-
conn.finish
|
|
301
|
-
return -1
|
|
302
|
-
end
|
|
303
|
-
Tep::APP.set_presence_pg_conn(conn)
|
|
304
|
-
worker_id = Sock.sphttp_getpid.to_s + "-" + Time.now.to_i.to_s
|
|
305
|
-
Tep::APP.set_presence_pg_worker_id(worker_id)
|
|
306
|
-
Tep::APP.set_presence_pg_enabled(1)
|
|
307
|
-
# Drop any rows from a prior worker that managed to leave
|
|
308
|
-
# stale entries with this same worker_id (unlikely thanks
|
|
309
|
-
# to the boot-epoch suffix, but defensive). Best-effort.
|
|
310
|
-
Tep::Presence.mirror_exec(
|
|
311
|
-
"DELETE FROM tep_presence WHERE worker_id = $1",
|
|
312
|
-
[worker_id])
|
|
313
|
-
# Register this worker's heartbeat row immediately. Apps
|
|
314
|
-
# refresh it periodically via Tep::Presence.heartbeat;
|
|
315
|
-
# prune_stale_workers deletes rows whose heartbeat is stale.
|
|
316
|
-
Tep::Presence.heartbeat
|
|
317
|
-
0
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def self.disable_pg_mirror
|
|
321
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
322
|
-
return 0
|
|
323
|
-
end
|
|
324
|
-
# Best-effort cleanup -- swallow PG errors (we're tearing the
|
|
325
|
-
# mirror down regardless) and still finish + disable below.
|
|
326
|
-
begin
|
|
327
|
-
r = Tep::APP.presence_pg_conn.exec_params(
|
|
328
|
-
"DELETE FROM tep_presence WHERE worker_id = $1",
|
|
329
|
-
[Tep::APP.presence_pg_worker_id])
|
|
330
|
-
r.clear
|
|
331
|
-
# Remove the heartbeat row so prune_stale_workers doesn't
|
|
332
|
-
# see this worker as live after we're gone.
|
|
333
|
-
r = Tep::APP.presence_pg_conn.exec_params(
|
|
334
|
-
"DELETE FROM tep_presence_worker WHERE worker_id = $1",
|
|
335
|
-
[Tep::APP.presence_pg_worker_id])
|
|
336
|
-
r.clear
|
|
337
|
-
rescue PG::Error
|
|
338
|
-
# swallow -- shutting the mirror down anyway
|
|
339
|
-
end
|
|
340
|
-
Tep::APP.presence_pg_conn.finish
|
|
341
|
-
Tep::APP.set_presence_pg_enabled(0)
|
|
342
|
-
0
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
# CREATE TABLE statement, kept here so apps that want to
|
|
346
|
-
# provision the schema separately (migration runners, etc.)
|
|
347
|
-
# can grab the canonical DDL. Idempotent via IF NOT EXISTS.
|
|
348
|
-
def self.schema_sql
|
|
349
|
-
"CREATE TABLE IF NOT EXISTS tep_presence (" +
|
|
350
|
-
"worker_id TEXT NOT NULL, " +
|
|
351
|
-
"topic TEXT NOT NULL, " +
|
|
352
|
-
"fd INTEGER NOT NULL, " +
|
|
353
|
-
"principal_id TEXT NOT NULL, " +
|
|
354
|
-
"kind TEXT NOT NULL, " +
|
|
355
|
-
"agent_id TEXT NOT NULL, " +
|
|
356
|
-
"since_ts BIGINT NOT NULL, " +
|
|
357
|
-
"status_state TEXT NOT NULL, " +
|
|
358
|
-
"status_note TEXT NOT NULL, " +
|
|
359
|
-
"status_until BIGINT NOT NULL, " +
|
|
360
|
-
"PRIMARY KEY (worker_id, topic, fd)" +
|
|
361
|
-
")"
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
# Heartbeat table -- one row per worker that's mirroring
|
|
365
|
-
# presence right now. Used by prune_stale_workers to identify
|
|
366
|
-
# crashed workers (no heartbeat updates in N seconds) and
|
|
367
|
-
# garbage-collect their orphan tep_presence rows.
|
|
368
|
-
def self.worker_schema_sql
|
|
369
|
-
"CREATE TABLE IF NOT EXISTS tep_presence_worker (" +
|
|
370
|
-
"worker_id TEXT PRIMARY KEY, " +
|
|
371
|
-
"last_seen_ts BIGINT NOT NULL" +
|
|
372
|
-
")"
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
# Refresh this worker's heartbeat row to the current Unix
|
|
376
|
-
# timestamp. Apps call this periodically (typical: from a
|
|
377
|
-
# before-filter, a Tep::Job tick, or an explicit timer fiber)
|
|
378
|
-
# so prune_stale_workers can tell live workers from crashed
|
|
379
|
-
# ones. No-op when the PG mirror isn't enabled, or when the
|
|
380
|
-
# mirror was opened on a different process and we're the
|
|
381
|
-
# post-fork child (worker_id is empty until enable_pg_mirror
|
|
382
|
-
# runs locally).
|
|
383
|
-
#
|
|
384
|
-
# Returns 1 if the heartbeat row was upserted, 0 if the call
|
|
385
|
-
# short-circuited (mirror disabled or no worker_id).
|
|
386
|
-
def self.heartbeat
|
|
387
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
388
|
-
return 0
|
|
389
|
-
end
|
|
390
|
-
wid = Tep::APP.presence_pg_worker_id
|
|
391
|
-
if wid.length == 0
|
|
392
|
-
return 0
|
|
393
|
-
end
|
|
394
|
-
begin
|
|
395
|
-
r = Tep::APP.presence_pg_conn.exec_params(
|
|
396
|
-
"INSERT INTO tep_presence_worker (worker_id, last_seen_ts) " +
|
|
397
|
-
"VALUES ($1, $2) " +
|
|
398
|
-
"ON CONFLICT (worker_id) DO UPDATE SET " +
|
|
399
|
-
" last_seen_ts = EXCLUDED.last_seen_ts",
|
|
400
|
-
[wid, Time.now.to_i.to_s])
|
|
401
|
-
r.clear
|
|
402
|
-
rescue PG::Error
|
|
403
|
-
return 0
|
|
404
|
-
end
|
|
405
|
-
1
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
# Prune crashed-worker rows. Deletes:
|
|
409
|
-
# 1. tep_presence_worker rows whose last_seen_ts is older than
|
|
410
|
-
# ttl_seconds (the worker's heartbeat is stale).
|
|
411
|
-
# 2. tep_presence rows whose worker_id has no surviving
|
|
412
|
-
# heartbeat (orphans left by the crashed worker).
|
|
413
|
-
#
|
|
414
|
-
# Apps call this periodically -- the canonical shape is a
|
|
415
|
-
# before-filter on a "/health" route that internal monitoring
|
|
416
|
-
# hits every 30s, or a Tep::Job that fires from a cron-like
|
|
417
|
-
# tick. Returns the number of tep_presence rows deleted.
|
|
418
|
-
#
|
|
419
|
-
# ttl_seconds should be at least 3x the app's typical
|
|
420
|
-
# heartbeat interval so a transient slow response doesn't
|
|
421
|
-
# evict a live worker. Default callers pass 90 (assumes 30s
|
|
422
|
-
# heartbeats).
|
|
423
|
-
def self.prune_stale_workers(ttl_seconds)
|
|
424
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
425
|
-
return 0
|
|
426
|
-
end
|
|
427
|
-
cutoff = Time.now.to_i - ttl_seconds
|
|
428
|
-
conn = Tep::APP.presence_pg_conn
|
|
429
|
-
begin
|
|
430
|
-
# Drop dead heartbeats first; the second DELETE then walks
|
|
431
|
-
# the worker_id space that's still alive.
|
|
432
|
-
r1 = conn.exec_params(
|
|
433
|
-
"DELETE FROM tep_presence_worker WHERE last_seen_ts < $1",
|
|
434
|
-
[cutoff.to_s])
|
|
435
|
-
r1.clear
|
|
436
|
-
# Now drop presence rows whose worker_id isn't in the live
|
|
437
|
-
# heartbeat table. NOT IN handles both crashed-and-pruned
|
|
438
|
-
# workers and workers that never registered (legacy rows
|
|
439
|
-
# from before this prune feature shipped).
|
|
440
|
-
r2 = conn.exec(
|
|
441
|
-
"DELETE FROM tep_presence " +
|
|
442
|
-
"WHERE worker_id NOT IN (SELECT worker_id FROM tep_presence_worker)")
|
|
443
|
-
n = r2.cmd_tuples
|
|
444
|
-
r2.clear
|
|
445
|
-
rescue PG::Error
|
|
446
|
-
return 0
|
|
447
|
-
end
|
|
448
|
-
n
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
# Best-effort mirror write: run an exec_params on the mirror conn
|
|
452
|
-
# and swallow any PG::Error. The PG mirror is advisory -- local
|
|
453
|
-
# presence is authoritative -- so a transient mirror failure must
|
|
454
|
-
# never propagate into the caller's request now that exec raises
|
|
455
|
-
# (matz/spinel#627 + #1041). Always returns 0.
|
|
456
|
-
def self.mirror_exec(sql, params)
|
|
457
|
-
begin
|
|
458
|
-
r = Tep::APP.presence_pg_conn.exec_params(sql, params)
|
|
459
|
-
r.clear
|
|
460
|
-
rescue PG::Error
|
|
461
|
-
# swallow -- advisory mirror, local presence is authoritative
|
|
462
|
-
end
|
|
463
|
-
0
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
# Mirror a track to PG. Called from track() when the PG
|
|
467
|
-
# mirror is enabled.
|
|
468
|
-
def self.mirror_insert(entry)
|
|
469
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
470
|
-
return 0
|
|
471
|
-
end
|
|
472
|
-
Tep::Presence.mirror_exec(
|
|
473
|
-
"INSERT INTO tep_presence " +
|
|
474
|
-
"(worker_id, topic, fd, principal_id, kind, agent_id, " +
|
|
475
|
-
" since_ts, status_state, status_note, status_until) " +
|
|
476
|
-
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) " +
|
|
477
|
-
"ON CONFLICT (worker_id, topic, fd) DO UPDATE SET " +
|
|
478
|
-
" principal_id = EXCLUDED.principal_id, " +
|
|
479
|
-
" kind = EXCLUDED.kind, " +
|
|
480
|
-
" agent_id = EXCLUDED.agent_id, " +
|
|
481
|
-
" since_ts = EXCLUDED.since_ts, " +
|
|
482
|
-
" status_state = EXCLUDED.status_state, " +
|
|
483
|
-
" status_note = EXCLUDED.status_note, " +
|
|
484
|
-
" status_until = EXCLUDED.status_until",
|
|
485
|
-
[
|
|
486
|
-
Tep::APP.presence_pg_worker_id,
|
|
487
|
-
entry.topic,
|
|
488
|
-
entry.fd.to_s,
|
|
489
|
-
entry.principal_id,
|
|
490
|
-
entry.kind.to_s,
|
|
491
|
-
entry.agent_id,
|
|
492
|
-
entry.since.to_s,
|
|
493
|
-
entry.status_state.to_s,
|
|
494
|
-
entry.status_note,
|
|
495
|
-
entry.status_until.to_s
|
|
496
|
-
])
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
# Mirror an untrack to PG.
|
|
500
|
-
def self.mirror_delete(topic, fd)
|
|
501
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
502
|
-
return 0
|
|
503
|
-
end
|
|
504
|
-
Tep::Presence.mirror_exec(
|
|
505
|
-
"DELETE FROM tep_presence " +
|
|
506
|
-
"WHERE worker_id = $1 AND topic = $2 AND fd = $3",
|
|
507
|
-
[Tep::APP.presence_pg_worker_id, topic, fd.to_s])
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
# Mirror a status update.
|
|
511
|
-
def self.mirror_status(topic, fd, state, note, until_ts)
|
|
512
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
513
|
-
return 0
|
|
514
|
-
end
|
|
515
|
-
Tep::Presence.mirror_exec(
|
|
516
|
-
"UPDATE tep_presence " +
|
|
517
|
-
"SET status_state = $4, status_note = $5, status_until = $6 " +
|
|
518
|
-
"WHERE worker_id = $1 AND topic = $2 AND fd = $3",
|
|
519
|
-
[Tep::APP.presence_pg_worker_id, topic, fd.to_s,
|
|
520
|
-
state.to_s, note, until_ts.to_s])
|
|
521
|
-
end
|
|
268
|
+
# ---- PG mirror (cross-worker visibility) ----
|
|
269
|
+
#
|
|
270
|
+
# The presence PG mirror is OPT-IN (#216). enable_pg_mirror /
|
|
271
|
+
# disable_pg_mirror / heartbeat / prune_stale_workers / mirror_exec /
|
|
272
|
+
# list_global / count_global / the schema DDL, and the REAL bodies of
|
|
273
|
+
# the mirror_* hooks below, live in lib/tep/pg.rb and only compile
|
|
274
|
+
# into apps that `require "tep/pg"`. Core Presence keeps no PG
|
|
275
|
+
# reference, so a non-PG app DCEs the libpq closure entirely.
|
|
276
|
+
#
|
|
277
|
+
# track / untrack / set_status call these mirror_* hooks
|
|
278
|
+
# unconditionally; the core definitions are no-ops. `require
|
|
279
|
+
# "tep/pg"` redefines them (last-definition-wins) with the real
|
|
280
|
+
# exec_params writes.
|
|
281
|
+
def self.mirror_insert(entry)
|
|
282
|
+
0
|
|
283
|
+
end
|
|
522
284
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
# snapshots -- mutating them doesn't write back to PG.
|
|
527
|
-
def self.list_global(topic)
|
|
528
|
-
result = [Tep::PresenceEntry.new("", "", :human, "", -1, 0)]
|
|
529
|
-
result.delete_at(0)
|
|
530
|
-
if Tep::APP.presence_pg_enabled == 0
|
|
531
|
-
return result
|
|
532
|
-
end
|
|
533
|
-
begin
|
|
534
|
-
r = Tep::APP.presence_pg_conn.exec_params(
|
|
535
|
-
"SELECT principal_id, kind, agent_id, fd, since_ts, " +
|
|
536
|
-
" status_state, status_note, status_until " +
|
|
537
|
-
"FROM tep_presence WHERE topic = $1 ORDER BY since_ts",
|
|
538
|
-
[topic])
|
|
539
|
-
rescue PG::Error
|
|
540
|
-
return result
|
|
541
|
-
end
|
|
542
|
-
i = 0
|
|
543
|
-
n = r.ntuples
|
|
544
|
-
while i < n
|
|
545
|
-
kind_sym = :human
|
|
546
|
-
if r.getvalue(i, 1) == "agent_for"
|
|
547
|
-
kind_sym = :agent_for
|
|
548
|
-
end
|
|
549
|
-
state_sym = :available
|
|
550
|
-
sstr = r.getvalue(i, 5)
|
|
551
|
-
if sstr == "busy"
|
|
552
|
-
state_sym = :busy
|
|
553
|
-
elsif sstr == "blocked"
|
|
554
|
-
state_sym = :blocked
|
|
555
|
-
end
|
|
556
|
-
e = Tep::PresenceEntry.new(
|
|
557
|
-
topic,
|
|
558
|
-
r.getvalue(i, 0),
|
|
559
|
-
kind_sym,
|
|
560
|
-
r.getvalue(i, 2),
|
|
561
|
-
r.getvalue(i, 3).to_i,
|
|
562
|
-
r.getvalue(i, 4).to_i)
|
|
563
|
-
e.status_state = state_sym
|
|
564
|
-
e.status_note = r.getvalue(i, 6)
|
|
565
|
-
e.status_until = r.getvalue(i, 7).to_i
|
|
566
|
-
result.push(e)
|
|
567
|
-
i += 1
|
|
568
|
-
end
|
|
569
|
-
r.clear
|
|
570
|
-
result
|
|
571
|
-
end
|
|
285
|
+
def self.mirror_delete(topic, fd)
|
|
286
|
+
0
|
|
287
|
+
end
|
|
572
288
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
end
|
|
577
|
-
begin
|
|
578
|
-
r = Tep::APP.presence_pg_conn.exec_params(
|
|
579
|
-
"SELECT count(*) FROM tep_presence WHERE topic = $1",
|
|
580
|
-
[topic])
|
|
581
|
-
rescue PG::Error
|
|
582
|
-
return 0
|
|
583
|
-
end
|
|
584
|
-
n = r.getvalue(0, 0).to_i
|
|
585
|
-
r.clear
|
|
586
|
-
n
|
|
587
|
-
end
|
|
289
|
+
def self.mirror_status(topic, fd, state, note, until_ts)
|
|
290
|
+
0
|
|
291
|
+
end
|
|
588
292
|
end
|
|
589
293
|
end
|
data/lib/tep/version.rb
CHANGED
data/lib/tep.rb
CHANGED
|
@@ -70,7 +70,10 @@ require_relative "tep/live_view"
|
|
|
70
70
|
require_relative "tep/server"
|
|
71
71
|
require_relative "tep/server_scheduled"
|
|
72
72
|
require_relative "tep/sqlite"
|
|
73
|
-
|
|
73
|
+
# tep/pg is OPT-IN (#216): NOT required here. An app that needs
|
|
74
|
+
# PostgreSQL does `require "tep/pg"`, which bin/tep splices in at build
|
|
75
|
+
# time (and the test suite requires explicitly). Keeping it out of the
|
|
76
|
+
# core require tree is what lets a non-PG app DCE the libpq closure.
|
|
74
77
|
require_relative "spinel_kit/json"
|
|
75
78
|
require_relative "spinel_kit/json_decoder"
|
|
76
79
|
require_relative "tep/mcp"
|
|
@@ -213,13 +216,7 @@ module Tep
|
|
|
213
216
|
APP.set_after(Filter.new)
|
|
214
217
|
APP.set_auth_filter(Filter.new)
|
|
215
218
|
APP.set_auth_bearer_secret("")
|
|
216
|
-
#
|
|
217
|
-
# these via set_broadcast_pg_conn / _channel / _enabled when a
|
|
218
|
-
# connect succeeds; the empty-conninfo seed below short-circuits
|
|
219
|
-
# before getting there, so we exercise the setters directly.
|
|
220
|
-
APP.set_broadcast_pg_enabled(0)
|
|
221
|
-
APP.set_broadcast_pg_channel("")
|
|
222
|
-
APP.set_broadcast_pg_conn(PG::Connection.new(""))
|
|
219
|
+
# (broadcast_pg setter seeds relocated to lib/tep/pg.rb -- #216)
|
|
223
220
|
APP.set_not_found(Handler.new)
|
|
224
221
|
# Type-seeding: methods that may not be called by a given user app
|
|
225
222
|
# would otherwise default their param C types to mrb_int and
|
|
@@ -275,19 +272,10 @@ module Tep
|
|
|
275
272
|
Tep::Broadcast.subscriber_count
|
|
276
273
|
Tep::Broadcast.clear
|
|
277
274
|
|
|
278
|
-
# Broadcast PG-backend seeds. enable_pg_backend("", "") tries to
|
|
279
|
-
# open a PG connection -- empty conninfo behaves the same as the
|
|
280
|
-
# PG::Connection.new("") seed above: connect fails, returns -1.
|
|
281
|
-
# The point is to pin parameter types on every cmeth.
|
|
282
|
-
Tep::Broadcast.enable_pg_backend("", "")
|
|
283
|
-
Tep::Broadcast.poll_pg_once(0)
|
|
284
|
-
Tep::Broadcast.disable_pg_backend
|
|
285
|
-
Tep::Broadcast.encode_wire("", "")
|
|
286
|
-
Tep::Broadcast.deliver_wire_local("0:")
|
|
287
275
|
Tep::Broadcast.publish_local_only("_seed", "")
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
276
|
+
# (Broadcast PG-backend seeds -- enable_pg_backend / poll_pg_once /
|
|
277
|
+
# disable_pg_backend / encode_wire / deliver_wire_local -- relocated
|
|
278
|
+
# to lib/tep/pg.rb, #216.)
|
|
291
279
|
|
|
292
280
|
# Presence type-seeding. Same pattern as Broadcast: pin every
|
|
293
281
|
# cmeth's param C types so compile units that don't otherwise
|
|
@@ -317,25 +305,10 @@ module Tep
|
|
|
317
305
|
Tep::Presence.encode_diff("join", _tep_seed_presence_entry)
|
|
318
306
|
Tep::Presence.publish_diff("join", _tep_seed_presence_entry)
|
|
319
307
|
Tep::Presence.sweep_expired_status
|
|
320
|
-
# PG mirror seeds
|
|
321
|
-
#
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
Tep::Presence.mirror_insert(_tep_seed_presence_entry)
|
|
325
|
-
Tep::Presence.mirror_delete("_seed", -1)
|
|
326
|
-
Tep::Presence.mirror_status("_seed", -1, :available, "", 0)
|
|
327
|
-
Tep::Presence.list_global("_seed")
|
|
328
|
-
Tep::Presence.count_global("_seed")
|
|
329
|
-
Tep::Presence.worker_schema_sql
|
|
330
|
-
Tep::Presence.heartbeat
|
|
331
|
-
Tep::Presence.prune_stale_workers(90)
|
|
332
|
-
Tep::Presence.disable_pg_mirror
|
|
333
|
-
# Same APP-setter-via-constant pattern as the broadcast_pg_conn
|
|
334
|
-
# seed: PG::Connection.new can't run inside App#initialize
|
|
335
|
-
# (Tep::APP is mid-construction; sched_current read segfaults).
|
|
336
|
-
APP.set_presence_pg_enabled(0)
|
|
337
|
-
APP.set_presence_pg_worker_id("")
|
|
338
|
-
APP.set_presence_pg_conn(PG::Connection.new(""))
|
|
308
|
+
# (Presence PG mirror seeds + presence_pg setter seeds relocated to
|
|
309
|
+
# lib/tep/pg.rb -- #216. The core mirror_insert/delete/status no-op
|
|
310
|
+
# stubs are already seeded by the track/set_status/untrack calls
|
|
311
|
+
# above.)
|
|
339
312
|
|
|
340
313
|
# LiveView type-seeding (chunk 4.1). The render_page + dispatch_event
|
|
341
314
|
# cmeths get pinned via top-level calls; the base-class mount /
|
|
@@ -387,72 +360,9 @@ module Tep
|
|
|
387
360
|
_tep_seed_db.close
|
|
388
361
|
end
|
|
389
362
|
|
|
390
|
-
# PG type-seeding
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
# server. The point is to pin parameter / return types on every
|
|
394
|
-
# public Connection / Result method so apps that don't exercise
|
|
395
|
-
# one method still compile cleanly.
|
|
396
|
-
_tep_seed_pg_conn = PG::Connection.new("")
|
|
397
|
-
_tep_seed_pg_conn.connected?
|
|
398
|
-
_tep_seed_pg_conn.status
|
|
399
|
-
_tep_seed_pg_conn.transaction_status
|
|
400
|
-
_tep_seed_pg_conn.server_version
|
|
401
|
-
_tep_seed_pg_conn.error_message
|
|
402
|
-
_tep_seed_pg_conn.escape_string("")
|
|
403
|
-
_tep_seed_pg_conn.escape_identifier("")
|
|
404
|
-
_tep_seed_pg_conn.escape_literal("")
|
|
405
|
-
_tep_seed_pg_conn.last_sqlstate = ""
|
|
406
|
-
_tep_seed_pg_conn.last_error_message = ""
|
|
407
|
-
_tep_seed_pg_conn.last_result_rh = -1
|
|
408
|
-
# Async surface seed -- calling these on a failed-conn instance
|
|
409
|
-
# is harmless (the C shim short-circuits on conn slot < 1).
|
|
410
|
-
_tep_seed_pg_conn.async_exec("")
|
|
411
|
-
_tep_seed_pg_seed_arr = [""]
|
|
412
|
-
_tep_seed_pg_seed_arr.delete_at(0)
|
|
413
|
-
_tep_seed_pg_conn.async_exec_params("", _tep_seed_pg_seed_arr)
|
|
414
|
-
# Async connect cmeth. Returns -1 for empty conninfo from a
|
|
415
|
-
# non-scheduled context (the shim's PQconnectStart-then-FAILED
|
|
416
|
-
# path), which is type-equivalent to the success path.
|
|
417
|
-
PG::Connection.async_connect("")
|
|
418
|
-
# LISTEN / NOTIFY surface (Tep::Broadcast PG backend lands here).
|
|
419
|
-
_tep_seed_pg_conn.listen("_seed")
|
|
420
|
-
_tep_seed_pg_conn.unlisten("_seed")
|
|
421
|
-
_tep_seed_pg_conn.notify("_seed", "")
|
|
422
|
-
_tep_seed_pg_conn.poll_notification(0)
|
|
423
|
-
_tep_seed_pg_conn.last_notify_channel
|
|
424
|
-
_tep_seed_pg_conn.last_notify_payload
|
|
425
|
-
_tep_seed_pg_res = PG::Result.new(-1)
|
|
426
|
-
_tep_seed_pg_res.ntuples
|
|
427
|
-
_tep_seed_pg_res.nfields
|
|
428
|
-
_tep_seed_pg_res.fname(0)
|
|
429
|
-
_tep_seed_pg_res.fnumber("")
|
|
430
|
-
_tep_seed_pg_res.ftype(0)
|
|
431
|
-
_tep_seed_pg_res.fformat(0)
|
|
432
|
-
_tep_seed_pg_res.fmod(0)
|
|
433
|
-
_tep_seed_pg_res.getvalue(0, 0)
|
|
434
|
-
_tep_seed_pg_res.getisnull(0, 0)
|
|
435
|
-
_tep_seed_pg_res.getlength(0, 0)
|
|
436
|
-
_tep_seed_pg_res.value(0, 0)
|
|
437
|
-
_tep_seed_pg_res.error_field(67)
|
|
438
|
-
_tep_seed_pg_res.cmd_status
|
|
439
|
-
_tep_seed_pg_res.cmd_tuples
|
|
440
|
-
_tep_seed_pg_res.error_message
|
|
441
|
-
_tep_seed_pg_res.sql_state
|
|
442
|
-
_tep_seed_pg_res.fields
|
|
443
|
-
_tep_seed_pg_res.values
|
|
444
|
-
_tep_seed_pg_res.column_values(0)
|
|
445
|
-
_tep_seed_pg_res.clear
|
|
446
|
-
_tep_seed_pg_conn.close
|
|
447
|
-
# Pool seed -- size 0 so we don't try to open real conns at load.
|
|
448
|
-
_tep_seed_pg_pool = PG::Pool.new("", 0)
|
|
449
|
-
_tep_seed_pg_pool.healthy?
|
|
450
|
-
_tep_seed_pg_pool.available
|
|
451
|
-
_tep_seed_pg_pool.size
|
|
452
|
-
_tep_seed_pg_pool.set_checkout_timeout_ms(0)
|
|
453
|
-
_tep_seed_pg_pool.close_all
|
|
454
|
-
# NB: don't checkout/checkin against the size-0 seed pool; it'd
|
|
455
|
-
# spin until timeout. The seed has @free.length=0 forever.
|
|
363
|
+
# (PG::Connection / Result / Pool type-seeding relocated to
|
|
364
|
+
# lib/tep/pg.rb -- #216. PG.Connection.new("") is a failed-conn
|
|
365
|
+
# instance, not a raise, so the seeds stay safe at module load.)
|
|
456
366
|
|
|
457
367
|
# SpinelKit::Json type-seeding. Pin every public method's parameter
|
|
458
368
|
# types so an app that uses one method but not another still
|
|
@@ -539,10 +449,19 @@ module Tep
|
|
|
539
449
|
Tep::Scheduler.clear
|
|
540
450
|
|
|
541
451
|
# Tep::Shell seed -- pin :str args at the FFI boundary.
|
|
452
|
+
#
|
|
453
|
+
# These run at MODULE LOAD, so the paths must be readable in EVERY
|
|
454
|
+
# deploy environment, not just gx10. `/etc/hostname` is absent in a
|
|
455
|
+
# bare container (e.g. Upsun); under the engine's now-correct
|
|
456
|
+
# ENOENT-raising File.read it threw at boot and 502'd the native
|
|
457
|
+
# serve_bin (tep#199 boot-hazard report). `/dev/null` exists on every
|
|
458
|
+
# POSIX target (Linux containers, macOS) and reads as empty, so it
|
|
459
|
+
# pins the same :str param type without the missing-file crash. The
|
|
460
|
+
# full fix -- no boot-time seed I/O at all -- is tep#199 (--rbs sig).
|
|
542
461
|
Tep::Shell.run(":")
|
|
543
462
|
Tep::Shell.run_limited(":", 1)
|
|
544
|
-
Tep::Shell.read("/
|
|
545
|
-
Tep::Shell.read_limited("/
|
|
463
|
+
Tep::Shell.read("/dev/null")
|
|
464
|
+
Tep::Shell.read_limited("/dev/null", 64)
|
|
546
465
|
|
|
547
466
|
# SpinelKit::Url seed -- the new split_url has to land at compile time.
|
|
548
467
|
SpinelKit::Url.split_url("http://x/")
|
data/spinel-ext.json
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
"source": "lib/tep/sphttp.c",
|
|
6
6
|
"cflags": ["-O2"]
|
|
7
7
|
},
|
|
8
|
+
{
|
|
9
|
+
"name": "sphttp",
|
|
10
|
+
"placeholder": "@TEP_SPHTTP_CFLAGS@",
|
|
11
|
+
"pkg_config": "openssl",
|
|
12
|
+
"pkg_config_fallback": "-lssl -lcrypto"
|
|
13
|
+
},
|
|
8
14
|
{
|
|
9
15
|
"name": "sqlite",
|
|
10
16
|
"placeholder": "@TEP_SQLITE_O@",
|
data/test/test_broadcast_pg.rb
CHANGED
data/test/test_pg.rb
CHANGED
|
@@ -42,6 +42,7 @@ class TestPg < TepTest
|
|
|
42
42
|
# in the compiled binary.
|
|
43
43
|
app_source <<~RB
|
|
44
44
|
require 'sinatra'
|
|
45
|
+
require "tep/pg" # opt-in PG backend (#216)
|
|
45
46
|
|
|
46
47
|
# The PG test app runs under the default prefork server. We
|
|
47
48
|
# exercise the async surface explicitly via /async_exec and
|
data/test/test_presence_pg.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tep
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ori Pekelman
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: spinel_kit
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.2'
|
|
26
40
|
description: |-
|
|
27
41
|
tep is a small Sinatra-style DSL targeting the Spinel AOT Ruby
|
|
28
42
|
compiler. The translator turns a Sinatra-classic source file into
|