mirror of
https://github.com/xroche/httrack.git
synced 2026-07-28 19:43:02 +03:00
* hts_newthread() leaves the pthread attributes object undestroyed when thread creation fails hts_newthread() folds pthread_attr_init(), pthread_attr_setstacksize() and pthread_create() into one short-circuit condition, and destroys the attributes object only on the branch where all three succeeded. If setstacksize or create fails (EAGAIN under thread or memory exhaustion being the realistic case), attr stays initialised for good. glibc allocates nothing for a default-initialised attr, so nothing actually leaks on Linux. This is a portability and hygiene fix rather than a live bug. The init has to come out of the chain instead of just gaining a destroy call: the shared condition cannot tell which of the three failed, and destroying an object pthread_attr_init() never initialised is undefined. The imbalance is invisible to a leak checker, so the test counts it directly. A small LD_PRELOAD shim refuses pthread_create() and records which attributes objects nobody destroyed afterwards: 1 undestroyed on master, 0 with the fix. Closes #772 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * Review fixes: tighter comments, and only attr-bearing refusals count Condense the two-line note above pthread_attr_init(), fix the "so that is can be independent" typo on the line the diff moves, and count a refused spawn as tested only when it carried an attributes object, so a NULL-attr create from elsewhere cannot satisfy the guard on its own. Note why the shim's fixed slot count and unlocked counters are enough. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> --------- Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
84 lines
2.7 KiB
C
84 lines
2.7 KiB
C
/* Forces pthread_create() to fail for 113_engine-threadattr-leak.test, and
|
|
tracks which attributes objects were left undestroyed after that failure
|
|
(#772). glibc allocates nothing for a default attr, so the imbalance is
|
|
invisible to a leak checker and has to be counted here. */
|
|
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* The tree builds with -fvisibility=hidden, which would hide the interposer. */
|
|
#define SHIM_EXPORT __attribute__((visibility("default")))
|
|
|
|
/* Every create is refused in the only mode this shim runs in, so the process
|
|
stays single-threaded: unlocked counters and a fixed 64 slots are enough. */
|
|
#define PENDING_MAX 64
|
|
|
|
static const void *pending[PENDING_MAX];
|
|
static unsigned int pending_n;
|
|
static unsigned int sabotaged;
|
|
|
|
SHIM_EXPORT int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
|
void *(*start)(void *), void *arg);
|
|
SHIM_EXPORT int pthread_attr_destroy(pthread_attr_t *attr);
|
|
|
|
SHIM_EXPORT int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
|
void *(*start)(void *), void *arg) {
|
|
static int (*real_create)(pthread_t *, const pthread_attr_t *,
|
|
void *(*) (void *), void *) = NULL;
|
|
|
|
if (getenv("THREADATTRFAIL") != NULL) {
|
|
/* only an attr-bearing spawn can leak one, so only those count as tested */
|
|
if (attr != NULL) {
|
|
sabotaged++;
|
|
if (pending_n < PENDING_MAX)
|
|
pending[pending_n++] = attr;
|
|
}
|
|
return EAGAIN;
|
|
}
|
|
if (real_create == NULL) {
|
|
*(void **) &real_create = dlsym(RTLD_NEXT, "pthread_create");
|
|
if (real_create == NULL)
|
|
return ENOSYS;
|
|
}
|
|
return real_create(thread, attr, start, arg);
|
|
}
|
|
|
|
SHIM_EXPORT int pthread_attr_destroy(pthread_attr_t *attr) {
|
|
static int (*real_destroy)(pthread_attr_t *) = NULL;
|
|
unsigned int i;
|
|
|
|
/* drop one entry, so an address reused by a later init still counts once */
|
|
for (i = pending_n; i > 0; i--) {
|
|
if (pending[i - 1] == attr) {
|
|
memmove(&pending[i - 1], &pending[i],
|
|
(pending_n - i) * sizeof(pending[0]));
|
|
pending_n--;
|
|
break;
|
|
}
|
|
}
|
|
if (real_destroy == NULL) {
|
|
*(void **) &real_destroy = dlsym(RTLD_NEXT, "pthread_attr_destroy");
|
|
if (real_destroy == NULL)
|
|
return ENOSYS;
|
|
}
|
|
return real_destroy(attr);
|
|
}
|
|
|
|
__attribute__((destructor)) static void threadattrfail_report(void) {
|
|
const char *const path = getenv("THREADATTRFAIL_LOG");
|
|
FILE *fp;
|
|
|
|
if (path == NULL)
|
|
return;
|
|
fp = fopen(path, "wb");
|
|
if (fp == NULL)
|
|
return;
|
|
fprintf(fp, "sabotaged %u\nundestroyed %u\n", sabotaged, pending_n);
|
|
fclose(fp);
|
|
}
|