commit f203fc805793cdb94851d95fe76fd2a3db1b86de
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-10T09:39:58Z |
| subject | Added bind_wait option |
commit f203fc805793cdb94851d95fe76fd2a3db1b86de
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-10T09:39:58Z
Added bind_wait option
---
docs/tinyproxy.conf.5.scd | 11 +++++++
src/bind.c | 42 +++++++++++++++++++++++++
src/bind.h | 14 +++++++++
src/compat_cpu.c | 18 +++++++++++
src/compat_cpu.h | 1 +
src/datagram_listener.c | 38 +++++++++++------------
src/file_conf.c | 1 +
src/route.c | 3 ++
src/route.h | 1 +
src/stream_listener.c | 78 ++++++++++++++++++++++++++++++++++++++++++-----
10 files changed, 179 insertions(+), 28 deletions(-)
diff --git a/docs/tinyproxy.conf.5.scd b/docs/tinyproxy.conf.5.scd
index 0d26e21..1c62f47 100644
--- a/docs/tinyproxy.conf.5.scd
+++ b/docs/tinyproxy.conf.5.scd
@@ -53,6 +53,17 @@ connect_timeout=SECONDS
Connection timeout for both listen and upstream.
Default: 5
+bind_wait=SECONDS
+ Wait up to SECONDS for the listen address to become available when
+ bind(2) fails with EADDRINUSE.
+
+ This is useful during rolling restarts where a replacement process may
+ start before the previous process has released the listen port. If the
+ address becomes available before the timeout expires, tinyproxy binds it
+ and continues startup. Otherwise startup fails.
+
+ The default is 0, which fails immediately.
+
broadcast_reply=listen
UDP only. Send upstream replies back to the original client from the listening endpoint.
diff --git a/src/bind.c b/src/bind.c
new file mode 100644
index 0000000..8ee3630
--- /dev/null
+++ b/src/bind.c
@@ -0,0 +1,42 @@
+#include "klog.h"
+#include "compat_cpu.h"
+
+#include "bind.h"
+
+#define BIND_WAIT_INTERVAL_MS 100
+
+int bind_with_wait(
+ evutil_socket_t fd,
+ const struct sockaddr *addr,
+ socklen_t addr_len,
+ const struct route *r)
+{
+ int waited_ms = 0;
+ int wait_ms = 1000 * r->opts.bind_wait_sec;
+
+ for (;;) {
+ if (bind(fd, addr, addr_len) == 0) {
+ if(0 < waited_ms) {
+ LOG_INFO("listener bound after wait",
+ "listen", _LOGV_ENDPOINT(&r->listen));
+ }
+ return 0;
+ }
+
+ int err = EVUTIL_SOCKET_ERROR();
+
+ if (err != EADDRINUSE || wait_ms <= 0 || waited_ms >= wait_ms) {
+ return err ? -err : -EIO;
+ }
+
+ if (waited_ms == 0) {
+ LOG_WARN("listener address in use, waiting to bind",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "timeout", _LOGV(r->opts.bind_wait_sec));
+ }
+
+ sleep_ms(BIND_WAIT_INTERVAL_MS);
+ waited_ms += BIND_WAIT_INTERVAL_MS;
+ }
+}
+
diff --git a/src/bind.h b/src/bind.h
new file mode 100644
index 0000000..0ce52c4
--- /dev/null
+++ b/src/bind.h
@@ -0,0 +1,14 @@
+#ifndef BIND_H
+#define BIND_H
+
+#include <event2/util.h>
+
+#include "route.h"
+
+int bind_with_wait(
+ evutil_socket_t fd,
+ const struct sockaddr *addr,
+ socklen_t addr_len,
+ const struct route *r);
+
+#endif
diff --git a/src/compat_cpu.c b/src/compat_cpu.c
index a7f4287..d0153d0 100644
--- a/src/compat_cpu.c
+++ b/src/compat_cpu.c
@@ -6,6 +6,9 @@
#include <windows.h>
#else
#include <unistd.h>
+
+#include <time.h>
+#include <errno.h>
#endif
int compat_cpu_count(void)
@@ -31,3 +34,18 @@ int compat_cpu_count(void)
return (int)n;
#endif
}
+
+void sleep_ms(unsigned int ms)
+{
+#ifdef _WIN32
+ Sleep(ms);
+#else
+ struct timespec ts;
+
+ ts.tv_sec = ms / 1000;
+ ts.tv_nsec = (long)(ms % 1000) * 1000000L;
+
+ while (nanosleep(&ts, &ts) < 0 && errno == EINTR) {
+ }
+#endif
+}
diff --git a/src/compat_cpu.h b/src/compat_cpu.h
index 4a3a77b..99d6421 100644
--- a/src/compat_cpu.h
+++ b/src/compat_cpu.h
@@ -2,5 +2,6 @@
#define COMPAT_CPU_H
int compat_cpu_count(void);
+void sleep_ms(unsigned int);
#endif
diff --git a/src/datagram_listener.c b/src/datagram_listener.c
index 6aaf141..8df02d0 100644
--- a/src/datagram_listener.c
+++ b/src/datagram_listener.c
@@ -4,6 +4,8 @@
#include "klog.h"
#include "route.h"
+#include "bind.h"
+#include "compat_cpu.h"
#include "datagram_listener.h"
#include "datagram_client.h"
#include "datagram_builtin.h"
@@ -12,29 +14,22 @@
#ifdef TINYPROXY_DEBUG
#include <stdlib.h>
-#ifndef _WIN32
-#include <unistd.h>
-#endif
static void tinyproxy_debug_race_sleep(const char *name)
{
const char *want = getenv("TINYPROXY_RACE_SLEEP");
- const char *delay_s = getenv("TINYPROXY_RACE_SLEEP_US");
- long delay_us;
+ const char *delay_s = getenv("TINYPROXY_RACE_SLEEP_MS");
+ long delay_ms;
if (!want || strcmp(want, name) != 0) {
return;
}
- delay_us = delay_s ? strtol(delay_s, NULL, 10) : 1000;
- if (delay_us <= 0) {
- delay_us = 1000;
+ delay_ms = delay_s ? strtol(delay_s, NULL, 10) : 1;
+ if (delay_ms <= 0) {
+ delay_ms = 1;
}
-#ifdef _WIN32
- Sleep((DWORD)((delay_us + 999) / 1000));
-#else
- usleep((useconds_t)delay_us);
-#endif
+ sleep_ms(delay_ms);
}
#else
static void tinyproxy_debug_race_sleep(const char *name)
@@ -439,17 +434,20 @@ static int bind_udp_datagram_listener(struct datagram_route_ctx *ctx)
return -err;
}
- if (bind(
- ctx->listen_fd,
- (const struct sockaddr *)&listen_addr,
- listen_addr_len
- ) < 0) {
- int err = EVUTIL_SOCKET_ERROR();
+ rc = bind_with_wait(
+ ctx->listen_fd,
+ (const struct sockaddr *)&listen_addr,
+ listen_addr_len,
+ ctx->route
+ );
+
+ if (rc < 0) {
+ int err = -rc;
LOG_ERROR("udp bind failed",
"err", _LOGV(evutil_socket_error_to_string(err))
);
- return -err;
+ return rc;
}
ctx->local_addr_len = sizeof(ctx->local_addr);
diff --git a/src/file_conf.c b/src/file_conf.c
index 0826d2c..9b01654 100644
--- a/src/file_conf.c
+++ b/src/file_conf.c
@@ -300,6 +300,7 @@ static int parse_route_options(char *s, struct route_options *opts)
PARSE_INT_OPT("idle_timeout", idle_timeout_sec);
PARSE_INT_OPT("connect_timeout", connect_timeout_sec);
+ PARSE_INT_OPT("bind_wait", bind_wait_sec);
PARSE_ENUM_OPT("broadcast_reply",
parse_broadcast_reply_mode,
broadcast_reply);
diff --git a/src/route.c b/src/route.c
index e7fc502..f16826f 100644
--- a/src/route.c
+++ b/src/route.c
@@ -55,6 +55,9 @@ void route_options_str(const struct route_options *opts, char *buf, size_t bufle
if(opts->connect_timeout_sec != ROUTE_DEFAULT_CONNECT_TIMEOUT_SEC) {
ADD_INT("connect_timeout", opts->connect_timeout_sec);
}
+ if(opts->bind_wait_sec != 0) {
+ ADD_INT("bind_wait", opts->bind_wait_sec);
+ }
if(opts->keep_alive) {
ADD_BOOL("keep_alive", opts->keep_alive);
}
diff --git a/src/route.h b/src/route.h
index 3017e73..b16f630 100644
--- a/src/route.h
+++ b/src/route.h
@@ -32,6 +32,7 @@ struct route_options {
int idle_timeout_sec;
int connect_timeout_sec;
+ int bind_wait_sec;
enum broadcast_reply_mode broadcast_reply;
};
diff --git a/src/stream_listener.c b/src/stream_listener.c
index 8b79117..eeee459 100644
--- a/src/stream_listener.c
+++ b/src/stream_listener.c
@@ -5,6 +5,7 @@
#include "klog.h"
#include "route.h"
+#include "bind.h"
#include "worker_pool.h"
#include "stream_listener.h"
#include "stream_conn.h"
@@ -69,6 +70,8 @@ static int bind_tcp_stream_listener(struct stream_route_ctx *ctx)
const struct route *r = ctx->route;
struct sockaddr_storage listen_addr;
socklen_t listen_addr_len;
+ evutil_socket_t fd;
+ struct evconnlistener *listener;
int rc;
rc = endpoint_to_sockaddr(&r->listen, &listen_addr, &listen_addr_len);
@@ -78,22 +81,81 @@ static int bind_tcp_stream_listener(struct stream_route_ctx *ctx)
return rc;
}
- ctx->listener = evconnlistener_new_bind(
+ fd = socket(listen_addr.ss_family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ int err = EVUTIL_SOCKET_ERROR();
+
+ LOG_ERROR("failed to create tcp listener socket",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ return err ? -err : -EIO;
+ }
+
+ if (evutil_make_listen_socket_reuseable(fd) < 0) {
+ int err = EVUTIL_SOCKET_ERROR();
+
+ LOG_ERROR("failed to make tcp listener reusable",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ evutil_closesocket(fd);
+ return err ? -err : -EIO;
+ }
+
+ if (evutil_make_socket_nonblocking(fd) < 0) {
+ int err = EVUTIL_SOCKET_ERROR();
+
+ LOG_ERROR("failed to make tcp listener nonblocking",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ evutil_closesocket(fd);
+ return err ? -err : -EIO;
+ }
+
+ rc = bind_with_wait(
+ fd,
+ (struct sockaddr *)&listen_addr,
+ listen_addr_len,
+ r
+ );
+ if (rc != 0) {
+ int err = -rc;
+
+ LOG_ERROR("failed to bind tcp listener socket",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ evutil_closesocket(fd);
+ return rc;
+ }
+
+ if (listen(fd, -1) < 0) {
+ int err = EVUTIL_SOCKET_ERROR();
+
+ LOG_ERROR("failed to listen on tcp listener socket",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ evutil_closesocket(fd);
+ return err ? -err : -EIO;
+ }
+
+ listener = evconnlistener_new(
ctx->accept_base,
accept_cb,
ctx,
- LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
+ LEV_OPT_CLOSE_ON_FREE,
-1,
- (struct sockaddr *)&listen_addr,
- listen_addr_len
+ fd
);
+ if (listener == NULL) {
+ int err = EVUTIL_SOCKET_ERROR();
- if (ctx->listener == NULL) {
- LOG_ERROR("evconnlistener_new_bind failed",
- "listen", _LOGV_ENDPOINT(&r->listen));
- return -EADDRINUSE;
+ LOG_ERROR("failed to create tcp listener",
+ "listen", _LOGV_ENDPOINT(&r->listen),
+ "err", _LOGV(evutil_socket_error_to_string(err)));
+ evutil_closesocket(fd);
+ return err ? -err : -EIO;
}
+ ctx->listener = listener;
return 0;
}