Remove fetch_config_from_primary, ha_find_and_connect_to_primary, and the tier-1/2/3 discovery path. Standbys never initiate outbound connections to the primary; the primary owns all inter-controller channels. Key changes: - ha.c: add primary heartbeat loop (ha_start_primary_heartbeat) that sends OPCode_nop to connected standbys every heartbeat_interval_ms; loop self-cancels on demotion via role check before re-arm. - ha.c: add outbound reconnect in primary_heartbeat_fn — for peers absent from standby_endpoints, open fresh outbound TLS connection (on_standby_connected sends ctrlhello; standby_ep_ctx_t mirrors vote_ep_ctx_t layout). - ha.c: remove ha_find_and_connect_to_primary + helpers (ha_raw_tcp_connect, ha_tcp_connect_hook); remove election_timeout_fn (superseded by raft_election_timeout_fn); remove fetch_config_from_primary call from ha_elect_evaluate_and_signal STANDBY branch. - cluster.c: OPCode_requestVote denial no longer reuses inbound connection; late joiners are bootstrapped via primary heartbeat outbound connect. - config_repl.c: remove connect_ssl_raw, do_one_config_fetch, fetch_config_from_primary (~250 LOC). - myapp.c: remove my_tag > 0 fetch branch; all standbys wait passively. - ha.h: remove ha_find_and_connect_to_primary declaration + macros + ha_tcp_connect_hook seam; add ha_start_primary_heartbeat declaration. - meson.build: remove test_ha_connect.c (tests deleted function). - controller_test.c: remove test_ha_connect registration. - CLAUDE.md, src/controller/CLAUDE.md: document passive standby model, inbound-vs-outbound connection ownership, async I/O constraint, heartbeat model. - request.h: add xce_request declaration (removes scattered local externs).
177 lines
7.8 KiB
C
177 lines
7.8 KiB
C
#if !defined(_REQUEST_H_)
|
||
|
||
#define _REQUEST_H_ 1
|
||
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
|
||
#include <openssl/ssl.h>
|
||
|
||
#include "netio.h"
|
||
#include "typemap.h"
|
||
#include "utils.h"
|
||
#include "xce/types.h"
|
||
|
||
typedef struct {
|
||
int wait;
|
||
} reqbar_t;
|
||
|
||
typedef int (*xce_check_fn_t)(void*);
|
||
|
||
extern int xce_request(endpoint_t *ep, xce_request_t *req);
|
||
extern int xce_encode(xce_request_t*, io_packet_t*);
|
||
extern int xce_decode(io_packet_t*, xce_request_t*);
|
||
|
||
extern int xce_reply(endpoint_t*, xce_peerinfo_t*, xce_reply_t*);
|
||
extern int xce_hello_req(endpoint_t*, char*);
|
||
extern int xce_nop_req(endpoint_t*);
|
||
extern int xce_shutdown_req(endpoint_t*);
|
||
extern void init_reply_by_req(xce_reply_t*, xce_request_t*);
|
||
extern int xce_join_req(endpoint_t*, int, hostattrs_t*);
|
||
extern int xce_pushconfig_req(endpoint_t*, xce_peerinfo_t*, xce_configdata_t*);
|
||
extern int xce_push_data_req(endpoint_t*, xce_peerinfo_t*, xce_data_t*,
|
||
xce_check_fn_t, void*, int);
|
||
|
||
extern int xce_workerlist_req(endpoint_t*, xce_peerinfo_t*);
|
||
extern int xce_reply_workerlist(endpoint_t*, uint64_t, int, xce_peerinfo_t*,
|
||
xce_workerlist_t*);
|
||
extern int xce_jobdispatch_req(endpoint_t*, xce_job_dispatch_t*);
|
||
extern int xce_jobsubmit_req(endpoint_t*, xce_job_submit_t*);
|
||
extern int xce_reply_jobdispatch(endpoint_t*, uint64_t, int32_t,
|
||
xce_peerinfo_t*);
|
||
extern int xce_reply_jobid(endpoint_t*, int, uint64_t, uint64_t,
|
||
xce_peerinfo_t*);
|
||
extern int xce_joblist_req(endpoint_t*, xce_peerinfo_t*);
|
||
extern int xce_reply_joblist(endpoint_t*, uint64_t, int, xce_peerinfo_t*,
|
||
xce_job_list_t*);
|
||
extern int xce_startjob_req(endpoint_t*, xce_job_exec_t*);
|
||
extern int xce_clusterconf_req(endpoint_t*);
|
||
extern int xce_reply_clusterconf(endpoint_t*, uint64_t, int, xce_peerinfo_t*,
|
||
xce_clusterconf_t*);
|
||
extern int xce_get_req(endpoint_t*, xce_get_t*);
|
||
extern int xce_reply_get(endpoint_t*, xce_peerinfo_t*, xce_reply_t*,
|
||
xce_check_fn_t, void*, int);
|
||
extern int xce_notify_req(endpoint_t*, xce_notify_data_t*);
|
||
extern int extract_data(dataset_t*, xce_data_t*);
|
||
extern int xce_update_req(endpoint_t*, xce_update_data_t*);
|
||
extern int xce_jobctrl_req(endpoint_t*, xce_jobctrl_t*);
|
||
extern int xce_queuectrl_req(endpoint_t*, xce_queuectrl_t*);
|
||
extern int xce_hostctrl_req(endpoint_t*, xce_hostctrl_t*);
|
||
extern int xce_runtask_req(endpoint_t*, xce_taskrun_t*);
|
||
extern int xce_set_pend_reason_req(endpoint_t*, xce_preason_set_t*);
|
||
extern int xce_clear_reason_req(endpoint_t*, xce_clear_reason_t*);
|
||
|
||
/*
|
||
* xce_replconfigpush_req_raw: send OP_replConfigPush carrying raw byte arrays.
|
||
* Bypasses the encode_ReplConfigPushRequest_ptr path (which maps sha256/bytes
|
||
* through xce_data_t — wrong type for raw bytes) by constructing the capnp
|
||
* message directly via capn_new_list8 + capn_setv8 + write_Request.
|
||
*/
|
||
extern int xce_replconfigpush_req_raw(endpoint_t *ep, xce_peerinfo_t *from,
|
||
uint64_t version_tag,
|
||
const uint8_t *sha256, size_t sha256_len,
|
||
const uint8_t *config_bytes,
|
||
size_t config_len);
|
||
|
||
/*
|
||
* xce_replconfig_reply_raw: primary sends an OP_replConfig reply with
|
||
* actual config bytes via endpoint (iomgr queued). Bypasses
|
||
* encode_ReplConfigReply_ptr (which maps sha256/bytes through xce_data_t).
|
||
*/
|
||
extern int xce_replconfig_reply_raw(endpoint_t *ep, xce_peerinfo_t *from,
|
||
uint64_t seq, uint64_t tag,
|
||
const uint8_t *sha256, size_t sha256_len,
|
||
const uint8_t *config, size_t config_len);
|
||
|
||
/*
|
||
* xce_replconfig_req_ssl: standby sends OP_replConfig over a raw SSL
|
||
* socket (no iomgr). Wire framing: uint64_t size + capnp payload
|
||
* (matches netio.c io_fsm_perform).
|
||
*/
|
||
extern int xce_replconfig_req_ssl(SSL *ssl, uint64_t version_tag);
|
||
|
||
/*
|
||
* xce_replconfig_reply_ssl_decode: standby reads + parses an
|
||
* OP_replConfig reply from a raw SSL socket. Output buffers are
|
||
* malloc'd and owned by the caller. Both *out_sha256 and *out_bytes
|
||
* may be NULL on the up_to_date == 1 fast path.
|
||
*/
|
||
extern int xce_replconfig_reply_ssl_decode(SSL *ssl,
|
||
int *out_up_to_date,
|
||
uint64_t *out_tag,
|
||
uint8_t **out_sha256,
|
||
uint8_t **out_bytes,
|
||
size_t *out_len);
|
||
|
||
/*
|
||
* xce_free_request: free an xce_request_t and all owned sub-fields,
|
||
* including the raw_sha256/raw_bytes fields on upgrade_config_req_t
|
||
* that the generated free_Request() does not know about.
|
||
* Use this everywhere instead of calling free_Request() directly.
|
||
*/
|
||
extern void xce_free_request(xce_request_t *req);
|
||
|
||
/*
|
||
* xce_upgrade_config_req_extract: extract sha256 + bytes from a decoded
|
||
* OP_upgradeConfig request. xce_decode() stores malloc'd copies in
|
||
* req->body.upgradeconfigrequestmsg->raw_sha256 / raw_bytes before
|
||
* capn_free() runs. Returns shallow pointers (owned by req); caller
|
||
* must NOT free them. Returns -EINVAL if fields are missing.
|
||
*/
|
||
extern int xce_upgrade_config_req_extract(const xce_request_t *req,
|
||
uint8_t **out_bytes,
|
||
size_t *out_len,
|
||
uint8_t **out_sha256);
|
||
|
||
/*
|
||
* xce_upgrade_config_req_extract_raw: parse OP_upgradeConfig request
|
||
* bytes from a raw on-wire packet buffer. Output buffers (*out_bytes,
|
||
* *out_sha256) are malloc'd and owned by the caller.
|
||
*/
|
||
extern int xce_upgrade_config_req_extract_raw(const uint8_t *pkt_data,
|
||
size_t pkt_sz,
|
||
uint8_t **out_bytes,
|
||
size_t *out_len,
|
||
uint8_t **out_sha256);
|
||
|
||
/* ── Task 10.2: Client-Side Transparent Retry (Req 7.1–7.6) ─────────────────
|
||
*
|
||
* xce_retry_config_t: configurable retry parameters with safe production defaults.
|
||
* Pass NULL to xce_request_with_retry() to use defaults.
|
||
*/
|
||
typedef struct {
|
||
int max_retries; /* default 20 */
|
||
int initial_retry_ms; /* default 50 */
|
||
int max_retry_ms; /* default 5000 */
|
||
double backoff_multiplier; /* default 2.0 (exponential) */
|
||
} xce_retry_config_t;
|
||
|
||
extern const xce_retry_config_t XCE_RETRY_DEFAULTS;
|
||
|
||
/*
|
||
* xce_request_with_retry: transparent retry wrapper for transient controller errors.
|
||
*
|
||
* - XCE_ERR_NOT_READY → backoff, retry same endpoint (Req 7.1)
|
||
* - XCE_ERR_STANDBY with redirect_host/port → reconnect, retry (Req 7.2)
|
||
* - XCE_ERR_STANDBY without redirect → backoff, retry (Req 7.2 fallback)
|
||
* - ECONNREFUSED / ETIMEDOUT → reconnect, retry (Req 7.3)
|
||
* - EPERM / EINVAL / ENOENT / EACCES → surface immediately (Req 7.4)
|
||
* - Exhausted max_retries → surface last error (Req 7.5)
|
||
* - Backoff jitter via rand_r() to prevent thundering herd (Req 7.6)
|
||
*
|
||
* NULL cfg → uses XCE_RETRY_DEFAULTS.
|
||
*/
|
||
extern int xce_request_with_retry(endpoint_t *ep,
|
||
xce_request_t *req,
|
||
const xce_retry_config_t *cfg);
|
||
|
||
/*
|
||
* xce_retry_send_hook: test-only injection point for the send+receive round-trip.
|
||
* When non-NULL, xce_request_with_retry() calls this instead of the real network path.
|
||
* Returns 0 on success (reply_out populated), -errno on transport failure.
|
||
*/
|
||
extern int (*xce_retry_send_hook)(endpoint_t *ep, xce_request_t *req,
|
||
xce_reply_t *reply_out);
|
||
|
||
#endif
|