• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

krakjoe / parallel / 30887082779

24 Jul 2026 05:50AM UTC coverage: 95.041% (-0.05%) from 95.095%
30887082779

push

github

web-flow
Add ThreadSanitizer CI (#390)

13 of 14 new or added lines in 4 files covered. (92.86%)

1 existing line in 1 file now uncovered.

2913 of 3065 relevant lines covered (95.04%)

5681.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.84
/src/parallel.c
1
/*
2
  +----------------------------------------------------------------------+
3
  | parallel                                                             |
4
  +----------------------------------------------------------------------+
5
  | Copyright (c) Joe Watkins 2019-2024                                  |
6
  +----------------------------------------------------------------------+
7
  | This source file is subject to version 3.01 of the PHP license,      |
8
  | that is bundled with this package in the file LICENSE, and is        |
9
  | available through the world-wide-web at the following url:           |
10
  | http://www.php.net/license/3_01.txt                                  |
11
  | If you did not receive a copy of the PHP license and are unable to   |
12
  | obtain it through the world-wide-web, please send a note to          |
13
  | license@php.net so we can mail you a copy immediately.               |
14
  +----------------------------------------------------------------------+
15
  | Author: krakjoe                                                      |
16
  +----------------------------------------------------------------------+
17
 */
18
#ifndef HAVE_PARALLEL_PARALLEL
19
#define HAVE_PARALLEL_PARALLEL
20

21
#include "parallel.h"
22

23
/* {{{ */
24
TSRM_TLS HashTable php_parallel_runtimes;
25

26
static struct {
27
        pthread_mutex_t     mutex;
28
        zend_string        *bootstrap;
29
        volatile zend_ulong running;
30
} php_parallel_globals;
31

32
#define PCG(e) php_parallel_globals.e
33
/* }}} */
34

35
/* {{{ */
36
typedef int (*php_sapi_deactivate_t)(void);
37
typedef size_t (*php_sapi_output_t)(const char *, size_t);
38

39
static php_sapi_deactivate_t php_sapi_deactivate_function;
40
static php_sapi_output_t     php_sapi_output_function;
41

42
static pthread_mutex_t       php_parallel_output_mutex = PTHREAD_MUTEX_INITIALIZER;
43
static TSRM_TLS bool         php_parallel_output_locked = false;
44

45
static size_t                php_parallel_output_function(const char *str, size_t len)
26,198✔
46
{
47
        size_t result;
26,198✔
48

49
        pthread_mutex_lock(&php_parallel_output_mutex);
26,198✔
50
        php_parallel_output_locked = true;
26,198✔
51

52
        result = php_sapi_output_function(str, len);
26,198✔
53

54
        php_parallel_output_locked = false;
26,198✔
55
        pthread_mutex_unlock(&php_parallel_output_mutex);
26,198✔
56

57
        return result;
26,198✔
58
} /* }}} */
59

60
/* {{{ */
61
ZEND_BEGIN_ARG_INFO_EX(php_parallel_bootstrap_arginfo, 0, 0, 1)
62
ZEND_ARG_TYPE_INFO(0, file, IS_STRING, 0)
63
ZEND_END_ARG_INFO()
64

65
static PHP_NAMED_FUNCTION(php_parallel_bootstrap)
162✔
66
{
67
        zend_string *bootstrap;
162✔
68

69
        ZEND_PARSE_PARAMETERS_START(1, 1)
162✔
70
        Z_PARAM_PATH_STR(bootstrap)
324✔
71
        ZEND_PARSE_PARAMETERS_END();
216✔
72

73
        pthread_mutex_lock(&PCG(mutex));
162✔
74

75
        if (PCG(bootstrap)) {
162✔
76
                php_parallel_exception_ex(php_parallel_runtime_error_bootstrap_ce, "\\parallel\\bootstrap already set to %s",
18✔
77
                                          ZSTR_VAL(PCG(bootstrap)));
78
                pthread_mutex_unlock(&PCG(mutex));
18✔
79
                return;
18✔
80
        }
81

82
        if (PCG(running)) {
144✔
83
                php_parallel_exception_ex(php_parallel_runtime_error_bootstrap_ce,
36✔
84
                                          "\\parallel\\bootstrap should be called once, "
85
                                          "before any calls to \\parallel\\run");
86
                pthread_mutex_unlock(&PCG(mutex));
36✔
87
                return;
36✔
88
        }
89

90
        PCG(bootstrap) = php_parallel_copy_string_interned(bootstrap);
108✔
91
        pthread_mutex_unlock(&PCG(mutex));
108✔
92
} /* }}} */
93

94
// This will return an idle runtime (aka thread) or spawn a new one, bootstap it
95
// and return that. This method is solely called by the userlands
96
// `\parallel\run()` function call.
97
/* {{{ */
98
static zend_always_inline php_parallel_runtime_t *php_parallel_runtimes_fetch()
510✔
99
{
100
        php_parallel_runtime_t *runtime;
510✔
101

102
        ZEND_HASH_FOREACH_PTR(&php_parallel_runtimes, runtime)
1,374✔
103
        {
104
                if (!php_parallel_scheduler_busy(runtime)) {
882✔
105
                        return runtime;
106
                }
107
        }
108
        ZEND_HASH_FOREACH_END();
109

110
        if (!(runtime = php_parallel_runtime_construct(PCG(bootstrap)))) {
492✔
111
                return NULL;
112
        }
113

114
        pthread_mutex_lock(&PCG(mutex));
492✔
115
        PCG(running)++;
492✔
116
        pthread_mutex_unlock(&PCG(mutex));
492✔
117

118
        return zend_hash_next_index_insert_ptr(&php_parallel_runtimes, runtime);
984✔
119
}
120

121
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_parallel_run_arginfo, 0, 1, \\parallel\\Future, 1)
122
ZEND_ARG_OBJ_INFO(0, task, Closure, 0)
123
ZEND_ARG_TYPE_INFO(0, argv, IS_ARRAY, 0)
124
ZEND_END_ARG_INFO()
125

126
static PHP_NAMED_FUNCTION(php_parallel_run)
510✔
127
{
128
        php_parallel_runtime_t *runtime;
510✔
129
        zval                   *closure = NULL;
510✔
130
        zval                   *argv = NULL;
510✔
131

132
        ZEND_PARSE_PARAMETERS_START(1, 2)
510✔
133
        Z_PARAM_OBJECT_OF_CLASS(closure, zend_ce_closure)
1,020✔
134
        Z_PARAM_OPTIONAL
510✔
135
        Z_PARAM_ARRAY(argv)
798✔
136
        ZEND_PARSE_PARAMETERS_END();
510✔
137

138
        runtime = php_parallel_runtimes_fetch();
510✔
139

140
        if (!EG(exception)) {
510✔
141
                php_parallel_scheduler_push(runtime, closure, argv, return_value);
510✔
142
        }
143
} /* }}} */
144

145
#ifdef ZEND_DEBUG
146
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_parallel_count_arginfo, 0, 0, IS_LONG, 0)
147
ZEND_END_ARG_INFO()
148

149
PHP_NAMED_FUNCTION(php_parallel_count) { RETURN_LONG(zend_hash_num_elements(&php_parallel_runtimes)); }
×
150
#endif
151

152
/* {{{ */
153
zend_function_entry php_parallel_functions[] = {
154
    ZEND_NS_FENTRY("parallel", bootstrap, php_parallel_bootstrap, php_parallel_bootstrap_arginfo, 0)
155
        ZEND_NS_FENTRY("parallel", run, php_parallel_run, php_parallel_run_arginfo, 0)
156
#ifdef ZEND_DEBUG
157
            ZEND_NS_FENTRY("parallel", count, php_parallel_count, php_parallel_count_arginfo, 0)
158
#endif
159
                PHP_FE_END}; /* }}} */
160

161
PHP_MINIT_FUNCTION(PARALLEL_CORE)
3,294✔
162
{
163
        if (strncmp(sapi_module.name, "cli", sizeof("cli") - 1) == SUCCESS) {
3,294✔
164
                php_sapi_deactivate_function = sapi_module.deactivate;
3,294✔
165

166
                sapi_module.deactivate = NULL;
3,294✔
167
        }
168

169
        memset(&php_parallel_globals, 0, sizeof(php_parallel_globals));
3,294✔
170

171
        php_sapi_output_function = sapi_module.ub_write;
3,294✔
172

173
        sapi_module.ub_write = php_parallel_output_function;
3,294✔
174

175
        PHP_MINIT(PARALLEL_HANDLERS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
176
        PHP_MINIT(PARALLEL_EXCEPTIONS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
177
        PHP_MINIT(PARALLEL_COPY)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
178
        PHP_MINIT(PARALLEL_SCHEDULER)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
179
        PHP_MINIT(PARALLEL_CHANNEL)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
180
        PHP_MINIT(PARALLEL_EVENTS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
181
        PHP_MINIT(PARALLEL_SYNC)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
182

183
        php_parallel_mutex_init(&PCG(mutex), 1);
3,294✔
184
        PCG(running) = 0;
3,294✔
185
        PCG(bootstrap) = NULL;
3,294✔
186

187
        return SUCCESS;
3,294✔
188
}
189

190
PHP_MSHUTDOWN_FUNCTION(PARALLEL_CORE)
3,294✔
191
{
192
        PHP_MSHUTDOWN(PARALLEL_SYNC)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
193
        PHP_MSHUTDOWN(PARALLEL_EVENTS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
194
        PHP_MSHUTDOWN(PARALLEL_CHANNEL)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
195
        PHP_MSHUTDOWN(PARALLEL_SCHEDULER)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
196
        PHP_MSHUTDOWN(PARALLEL_COPY)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
197
        PHP_MSHUTDOWN(PARALLEL_EXCEPTIONS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
198
        PHP_MSHUTDOWN(PARALLEL_HANDLERS)(INIT_FUNC_ARGS_PASSTHRU);
3,294✔
199

200
        php_parallel_mutex_destroy(&PCG(mutex));
3,294✔
201

202
        if (strncmp(sapi_module.name, "cli", sizeof("cli") - 1) == SUCCESS) {
3,294✔
203
                sapi_module.deactivate = php_sapi_deactivate_function;
3,294✔
204
        }
205

206
        sapi_module.ub_write = php_sapi_output_function;
3,294✔
207

208
        return SUCCESS;
3,294✔
209
}
210

211
static void php_parallel_runtimes_release(zval *zv)
492✔
212
{
213
        php_parallel_runtime_t *runtime = (php_parallel_runtime_t *)Z_PTR_P(zv);
492✔
214

215
        OBJ_RELEASE(&runtime->std);
492✔
216
        pthread_mutex_lock(&PCG(mutex));
492✔
217
        PCG(running)--;
492✔
218
        pthread_mutex_unlock(&PCG(mutex));
492✔
219
}
492✔
220

221
PHP_RINIT_FUNCTION(PARALLEL_CORE)
5,532✔
222
{
223
        PHP_RINIT(PARALLEL_COPY)(INIT_FUNC_ARGS_PASSTHRU);
5,532✔
224

225
        zend_hash_init(&php_parallel_runtimes, 16, NULL, php_parallel_runtimes_release, 0);
5,532✔
226

227
        return SUCCESS;
5,532✔
228
}
229

230
PHP_RSHUTDOWN_FUNCTION(PARALLEL_CORE)
5,532✔
231
{
232
        zend_hash_destroy(&php_parallel_runtimes);
5,532✔
233

234
        PHP_RSHUTDOWN(PARALLEL_COPY)(INIT_FUNC_ARGS_PASSTHRU);
5,532✔
235

236
        // A `zend_bailout()` from the SAPI writer skips the normal unlock.
237
        // See https://github.com/krakjoe/parallel/issues/313 for more details.
238
        if (UNEXPECTED(CG(unclean_shutdown) == 1 && php_parallel_output_locked)) {
5,532✔
NEW
239
                php_parallel_output_locked = false;
×
UNCOV
240
                pthread_mutex_unlock(&php_parallel_output_mutex);
×
241
        }
242

243
        return SUCCESS;
5,532✔
244
}
245
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc