Change waiting loops to semaphores for child counters

This commit is contained in:
Vladimir Dubrovin 2026-10-10 23:10:57 +03:00
parent d4eaf5acc8
commit 77f8870c5d
9 changed files with 288 additions and 68 deletions

View File

@ -415,11 +415,12 @@ set(3PROXY_CORE_SOURCES
# Object libraries for common sources (shared between executables)
# ============================================================================
# Common object library (sockmap, sockgetchar, common, log)
# Common object library (sockmap, sockgetchar, common, sem, log)
add_library(common_obj OBJECT
src/sockmap.c
src/sockgetchar.c
src/common.c
src/sem.c
src/log.c
)
target_include_directories(common_obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

View File

@ -13,6 +13,9 @@ udpsockmap$(OBJSUFFICS): udpsockmap.c proxy.h structures.h
common$(OBJSUFFICS): common.c proxy.h structures.h
$(CC) $(CFLAGS) common.c
sem$(OBJSUFFICS): sem.c proxy.h structures.h
$(CC) $(CFLAGS) sem.c
plugins$(OBJSUFFICS): plugins.c proxy.h structures.h
$(CC) $(CFLAGS) plugins.c
@ -56,32 +59,32 @@ socks$(OBJSUFFICS): socks.c proxy.h structures.h proxymain.c
3proxy$(OBJSUFFICS): 3proxy.c proxy.h structures.h
$(CC) $(CFLAGS) 3proxy.c
$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS): sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS): sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)proxy$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) proxy$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS): sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS): sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)pop3p$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) pop3p$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS): sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS): sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)imapp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) imapp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS): sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) base64$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS): sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) base64$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)smtpp$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) smtpp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) base64$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS): sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) ftp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS): sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) ftp$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) $(COMPATLIBS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)ftppr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) ftppr$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) ftp$(OBJSUFFICS) $(COMPATLIBS) $(LIBS)
$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)socks$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) socks$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tcppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tcppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS): sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) hash$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)udppm$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) udppm$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) hash$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) $(LIBS)
$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS): sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS)
$(LN) $(LNOUT)$(BUILDDIR)$(PREFIX)tlspr$(EXESUFFICS) $(LDFLAGS) sockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) tlspr$(OBJSUFFICS) log$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) $(LIBS)
mainfunc$(OBJSUFFICS): proxy.h structures.h proxymain.c
$(CC) $(COUT)mainfunc$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)MODULEMAINFUNC=mainfunc proxymain.c
@ -194,6 +197,6 @@ ssl$(OBJSUFFICS): ssl.c structures.h proxy.h ssl.h
pcre$(OBJSUFFICS): pcre.c structures.h
$(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c
$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(VERSIONDEP)
$(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)
$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(VERSIONDEP)
$(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)

View File

@ -187,33 +187,6 @@ size_t threadstacksize(int extra){
if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN;
return (size_t)size;
}
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount){
sem->count = count;
sem->maxcount = maxcount;
if(pthread_mutex_init(&sem->mutex, NULL)) return 1;
if(pthread_cond_init(&sem->cond, NULL)){
pthread_mutex_destroy(&sem->mutex);
return 1;
}
return 0;
}
void _3proxy_sem_lock_f(_3proxy_sem_t *sem){
pthread_mutex_lock(&sem->mutex);
while(!sem->count) pthread_cond_wait(&sem->cond, &sem->mutex);
sem->count--;
pthread_mutex_unlock(&sem->mutex);
}
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem){
pthread_mutex_lock(&sem->mutex);
if(sem->count < sem->maxcount){
sem->count++;
pthread_cond_signal(&sem->cond);
}
pthread_mutex_unlock(&sem->mutex);
}
#endif
struct extparam conf = {

View File

@ -322,13 +322,23 @@ extern _3proxy_sem_t udpinit;
#define _3proxy_sem_init(x, count, maxcount) (((x) = CreateSemaphore(NULL, (count), (maxcount), NULL))? 0 : 1)
#define _3proxy_sem_lock(x) WaitForSingleObject(x, INFINITE)
#define _3proxy_sem_unlock(x) ReleaseSemaphore(x, 1, NULL)
#define _3proxy_sem_init_timed(x, count, maxcount) _3proxy_sem_init(x, (count), (maxcount))
#define _3proxy_sem_timedlock(x, ms) (WaitForSingleObject(x, ms) != WAIT_OBJECT_0)
#define _3proxy_sem_destroy(x) CloseHandle(x)
#else
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount);
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount, int timed);
void _3proxy_sem_lock_f(_3proxy_sem_t *sem);
int _3proxy_sem_timedlock_f(_3proxy_sem_t *sem, unsigned ms);
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem);
#define _3proxy_sem_init(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount))
void _3proxy_sem_destroy_f(_3proxy_sem_t *sem);
#define _3proxy_sem_init(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount), 0)
/* for a semaphore waited on with a timeout: the wait is not stretched or cut
short by the system time being changed, where the platform allows it */
#define _3proxy_sem_init_timed(x, count, maxcount) _3proxy_sem_init_f(&x, (count), (maxcount), 1)
#define _3proxy_sem_lock(x) _3proxy_sem_lock_f(&x)
#define _3proxy_sem_timedlock(x, ms) _3proxy_sem_timedlock_f(&x, (ms))
#define _3proxy_sem_unlock(x) _3proxy_sem_unlock_f(&x)
#define _3proxy_sem_destroy(x) _3proxy_sem_destroy_f(&x)
#endif

View File

@ -308,6 +308,7 @@ int MODULEMAINFUNC (int argc, char** argv){
FILE *fp = NULL;
struct linger lg;
int nlog = 5000;
int haveslot = 0, nfull = 0;
#ifdef __linux__
int saved_nsfd = -1;
#endif
@ -990,14 +991,19 @@ int MODULEMAINFUNC (int argc, char** argv){
for (;;) {
for(;;){
while((conf.paused == srv.paused && srv.childcount >= srv.maxchild)){
nlog++;
if(!srv.silent && nlog > 5000) {
/* A connection is only taken with a slot of maxchild in hand, which
goes to its child; a slot not handed on is kept for the next one.
The wait is timed to notice a reload. */
while(!haveslot && conf.paused == srv.paused){
if(!_3proxy_sem_timedlock(srv.childslots, 1000)){
haveslot = 1;
nfull = 0;
break;
}
if(!srv.silent && !(nfull++ % 5)) {
sprintf((char *)buf, "Warning: too many connected clients (%d/%d)", srv.childcount, srv.maxchild);
dolog(&defparam, buf);
nlog = 0;
}
usleep(SLEEPTIME);
}
if (iscbc) break;
if (conf.paused != srv.paused) break;
@ -1213,6 +1219,7 @@ int MODULEMAINFUNC (int argc, char** argv){
}
else {
srv.childcount++;
haveslot = 0;
newparam->threadid = (uint64_t)thread;
}
_3proxy_mutex_unlock(&srv.counter_mutex);
@ -1230,6 +1237,8 @@ int MODULEMAINFUNC (int argc, char** argv){
#endif
if(!srv.silent) srv.logfunc(&defparam, (unsigned char *)"Exiting thread");
/* srvfree() waits for every slot to come back, this one included */
if(haveslot) _3proxy_sem_unlock(srv.childslots);
srvfree(&srv);
#ifndef _WIN32
@ -1284,6 +1293,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){
param->remsock = param->clisock = param->ctrlsock = param->ctrlsocksrv = INVALID_SOCKET;
*SAFAMILY(&param->req) = *SAFAMILY(&param->sinsl) = *SAFAMILY(&param->sinsr) = *SAFAMILY(&param->sincr) = *SAFAMILY(&param->sincl) = AF_INET;
_3proxy_mutex_init(&srv->counter_mutex);
(void)_3proxy_sem_init_timed(srv->childslots, srv->maxchild, srv->maxchild);
#ifndef NOUDPMAIN
if(!udpinited){
(void)_3proxy_sem_init(udpinit, 1, 1);
@ -1329,6 +1339,8 @@ void srvinit2(struct srvparam * srv, struct clientparam *param){
}
void srvfree(struct srvparam * srv){
int i;
if(srv->srvsock != INVALID_SOCKET) {
so._closesocket(srv->so.state, srv->srvsock);
#ifdef WITH_UN
@ -1339,16 +1351,13 @@ void srvfree(struct srvparam * srv){
if(srv->cbsock != INVALID_SOCKET) so._closesocket(srv->so.state, srv->cbsock);
srv->cbsock = INVALID_SOCKET;
srv->service = S_ZOMBIE;
/* under the mutex, so the last child has let go of it before it is destroyed */
for(;;){
struct clientparam *child;
_3proxy_mutex_lock(&srv->counter_mutex);
child = srv->child;
_3proxy_mutex_unlock(&srv->counter_mutex);
if(!child) break;
usleep(SLEEPTIME * 100);
}
/* Every child returns its slot as the last thing it does with the service,
so once all of them are back no child is left. A child posts its slot
holding the mutex: taking the mutex once more waits for the last one to
have let go of both before they are destroyed. */
for(i = srv->maxchild; i; i--) _3proxy_sem_lock(srv->childslots);
_3proxy_mutex_lock(&srv->counter_mutex);
_3proxy_mutex_unlock(&srv->counter_mutex);
#ifndef STDMAIN
if(srv->filter){
while(srv->nfilters){
@ -1367,6 +1376,7 @@ void srvfree(struct srvparam * srv){
if(srv->authfuncs)freeauth(srv->authfuncs);
#endif
_3proxy_mutex_destroy(&srv->counter_mutex);
_3proxy_sem_destroy(srv->childslots);
if(srv->target) free(srv->target);
if(srv->logtarget) free(srv->logtarget);
if(srv->logformat) free(srv->logformat);
@ -1454,6 +1464,9 @@ void freeparam(struct clientparam * param) {
param->next->prev = param->prev;
}
(param->srv->childcount)--;
/* under the mutex too: srvfree() may destroy the semaphore as
soon as the mutex shows the last child gone */
_3proxy_sem_unlock(param->srv->childslots);
}
_3proxy_mutex_unlock(&param->srv->counter_mutex);
}

110
src/sem.c Normal file
View File

@ -0,0 +1,110 @@
/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
/* Counting semaphores for POSIX systems, built on a mutex and a condition
variable: unnamed POSIX semaphores are not available everywhere (macOS)
and have no timed wait there. Windows uses its own semaphores, see proxy.h. */
#include "proxy.h"
#ifndef _WIN32
/* macOS has CLOCK_MONOTONIC but no pthread_condattr_setclock(); a relative
wait is used there instead, which the system time does not affect either */
#if defined(CLOCK_MONOTONIC) && !defined(__APPLE__)
#define SEM_MONOTONIC
#endif
int _3proxy_sem_init_f(_3proxy_sem_t *sem, unsigned count, unsigned maxcount, int timed){
int res;
sem->count = count;
sem->maxcount = maxcount;
sem->monotonic = 0;
if(pthread_mutex_init(&sem->mutex, NULL)) return 1;
#ifdef SEM_MONOTONIC
if(timed){
pthread_condattr_t attr;
if(!pthread_condattr_init(&attr)){
if(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) sem->monotonic = 1;
res = pthread_cond_init(&sem->cond, &attr);
pthread_condattr_destroy(&attr);
}
else res = pthread_cond_init(&sem->cond, NULL);
}
else
#endif
res = pthread_cond_init(&sem->cond, NULL);
if(res){
pthread_mutex_destroy(&sem->mutex);
return 1;
}
return 0;
}
void _3proxy_sem_lock_f(_3proxy_sem_t *sem){
pthread_mutex_lock(&sem->mutex);
while(!sem->count) pthread_cond_wait(&sem->cond, &sem->mutex);
sem->count--;
pthread_mutex_unlock(&sem->mutex);
}
/* Returns 0 with the semaphore taken, nonzero if ms passed first. */
int _3proxy_sem_timedlock_f(_3proxy_sem_t *sem, unsigned ms){
struct timespec ts;
int res = 0;
#ifdef __APPLE__
ts.tv_sec = ms / 1000;
ts.tv_nsec = (long)(ms % 1000) * 1000000;
pthread_mutex_lock(&sem->mutex);
while(!sem->count && !res) res = pthread_cond_timedwait_relative_np(&sem->cond, &sem->mutex, &ts);
#else
/* the deadline is on the clock the condition variable was set up with */
#ifdef SEM_MONOTONIC
if(sem->monotonic) clock_gettime(CLOCK_MONOTONIC, &ts);
else
#endif
{
struct timeval tv;
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec;
ts.tv_nsec = tv.tv_usec * 1000;
}
ts.tv_sec += ms / 1000;
ts.tv_nsec += (long)(ms % 1000) * 1000000;
if(ts.tv_nsec >= 1000000000){
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
pthread_mutex_lock(&sem->mutex);
while(!sem->count && !res) res = pthread_cond_timedwait(&sem->cond, &sem->mutex, &ts);
#endif
if(sem->count){
sem->count--;
res = 0;
}
pthread_mutex_unlock(&sem->mutex);
return res;
}
void _3proxy_sem_unlock_f(_3proxy_sem_t *sem){
pthread_mutex_lock(&sem->mutex);
if(sem->count < sem->maxcount){
sem->count++;
pthread_cond_signal(&sem->cond);
}
pthread_mutex_unlock(&sem->mutex);
}
void _3proxy_sem_destroy_f(_3proxy_sem_t *sem){
pthread_cond_destroy(&sem->cond);
pthread_mutex_destroy(&sem->mutex);
}
#endif

View File

@ -47,6 +47,7 @@ typedef struct _3proxy_sem_s {
pthread_cond_t cond;
unsigned count;
unsigned maxcount;
int monotonic; /* timed waits measured on CLOCK_MONOTONIC */
} _3proxy_sem_t;
#else
#include <winsock2.h>
@ -635,6 +636,7 @@ struct srvparam {
#endif
PROXYSOCKADDRTYPE extsa;
_3proxy_mutex_t counter_mutex;
_3proxy_sem_t childslots; /* maxchild of them, one held by each child */
struct pollfd fds;
FILE *stdlog;
unsigned char * target;

View File

@ -69,7 +69,7 @@ means to stand on its own should start with one - otherwise an earlier
## What is not covered yet
41 of the 112 configuration commands appear in a test, and the count says
42 of the 112 configuration commands appear in a test, and the count says
nothing about service options: the IPv6 case, for instance, exercises -4,
-6, -46, -64 and -i without adding a command to it. What follows is
roughly the order worth working through: how much of the product a gap
@ -78,12 +78,13 @@ covers, and how much of a fixture it needs.
### Traffic limits and accounting
`bandlimin` `bandlimout` `nobandlimin` `nobandlimout` `connlim` `noconnlim`
`countin` `countout` `countall` and the `no*` forms, `maxconn`.
`countin` `countout` `countall` and the `no*` forms.
Cheap and worth doing first: `data?size=` and a stopwatch measure a
bandwidth limit, and the admin counters page already shows what a counter
holds. `countin` appears in a configuration today but nothing checks that it
counts. `connlim` and `maxconn` need concurrent connections.
counts. `connlim` needs concurrent connections, held open the way the
`maxconn` case holds them.
### The mail proxies

107
tests/cases/maxconn.py Normal file
View File

@ -0,0 +1,107 @@
"""maxconn: a service serves at most that many connections at once, and a
connection over the limit waits for one of them to finish."""
import os
import signal
import socket
import time
def run(t):
# The origin runs in a process of its own, so the limit only applies to
# the mapper in front of it.
origin = t.free_port()
t.start("maxconn_origin", f"""
auth iponly
allow *
http echo * /echo**
httpsrv -p{origin}
""", ports=[origin])
mapped = t.free_port()
server = t.start("maxconn", f"""
log
maxconn 2
auth iponly
allow *
tcppm {mapped} 127.0.0.1 {origin}
""", ports=[mapped])
def connect():
sock = socket.create_connection(("127.0.0.1", mapped), 5)
opened.append(sock)
return sock
def served(sock, timeout):
"""Ask over a kept alive connection; True once the whole reply is in,
so the connection is ready for the next request."""
sock.sendall(b"GET /echo HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
sock.settimeout(timeout)
data = b""
try:
while b"\r\n\r\n" not in data:
chunk = sock.recv(4096)
if not chunk:
return False
data += chunk
head, body = data.split(b"\r\n\r\n", 1)
length = 0
for line in head.split(b"\r\n")[1:]:
name, _, value = line.partition(b":")
if name.strip().lower() == b"content-length":
length = int(value)
while len(body) < length:
chunk = sock.recv(4096)
if not chunk:
return False
body += chunk
return head.startswith(b"HTTP/1.1 200")
except OSError:
return False
opened = []
try:
# the probe that saw the port open holds a slot until its session
# ends, so wait for both slots to be free before taking them
deadline = time.time() + 5
while True:
first, second = connect(), connect()
both = served(first, 1) and served(second, 1)
if both or time.time() > deadline:
break
first.close()
second.close()
t.eq(True, both, "two connections are served under maxconn 2")
third = connect()
t.eq(False, served(third, 1.5),
"a third connection waits while both slots are held")
t.contains(t.wait_output(server, "too many connected clients"),
"too many connected clients",
"a service at its limit says so in the log")
first.close()
t.eq(True, served(third, 5),
"the waiting connection is served once a slot is freed")
t.eq(True, served(second, 5),
"a connection holding a slot keeps being served")
if os.name == "nt":
t.skip("reload while at the limit (no SIGUSR1 on Windows)")
else:
waiting = connect()
t.eq(False, served(waiting, 1),
"the limit holds again with two connections open")
server.proc.send_signal(signal.SIGUSR1)
# A connection made the instant the signal arrives can land on
# the listener being replaced and be reset; give the new service
# the moment it needs.
time.sleep(1.5)
fresh = connect()
t.eq(True, served(fresh, 5),
"a reload while the limit is reached starts a service with "
"its slots free")
t.eq(None, server.proc.poll(), "the process survives the reload")
finally:
for sock in opened:
sock.close()