xref: /unit/src/nxt_java.c (revision 1980:43553aa72111)
1 
2 /*
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 
7 #include <jni.h>
8 
9 #include <nxt_main.h>
10 #include <nxt_runtime.h>
11 #include <nxt_router.h>
12 #include <nxt_unit.h>
13 #include <nxt_unit_field.h>
14 #include <nxt_unit_request.h>
15 #include <nxt_unit_response.h>
16 #include <nxt_unit_websocket.h>
17 
18 #include <java/nxt_jni.h>
19 
20 #include "java/nxt_jni_Thread.h"
21 #include "java/nxt_jni_Context.h"
22 #include "java/nxt_jni_Request.h"
23 #include "java/nxt_jni_Response.h"
24 #include "java/nxt_jni_InputStream.h"
25 #include "java/nxt_jni_OutputStream.h"
26 #include "java/nxt_jni_URLClassLoader.h"
27 
28 #include "nxt_jars.h"
29 
30 #include NXT_JAVA_MOUNTS_H
31 
32 static nxt_int_t nxt_java_setup(nxt_task_t *task, nxt_process_t *process,
33     nxt_common_app_conf_t *conf);
34 static nxt_int_t nxt_java_start(nxt_task_t *task,
35     nxt_process_data_t *data);
36 static void nxt_java_request_handler(nxt_unit_request_info_t *req);
37 static void nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws);
38 static void nxt_java_close_handler(nxt_unit_request_info_t *req);
39 static int nxt_java_ready_handler(nxt_unit_ctx_t *ctx);
40 static void *nxt_java_thread_func(void *main_ctx);
41 static int nxt_java_init_threads(nxt_java_app_conf_t *c);
42 static void nxt_java_join_threads(nxt_unit_ctx_t *ctx,
43     nxt_java_app_conf_t *c);
44 
45 static uint32_t  compat[] = {
46     NXT_VERNUM, NXT_DEBUG,
47 };
48 
49 char  *nxt_java_modules;
50 
51 static pthread_t       *nxt_java_threads;
52 static pthread_attr_t  *nxt_java_thread_attr;
53 
54 
55 #define NXT_STRING(x)   _NXT_STRING(x)
56 #define _NXT_STRING(x)  #x
57 
58 NXT_EXPORT nxt_app_module_t  nxt_app_module = {
59     sizeof(compat),
60     compat,
61     nxt_string("java"),
62     NXT_STRING(NXT_JAVA_VERSION),
63     nxt_java_mounts,
64     nxt_nitems(nxt_java_mounts),
65     nxt_java_setup,
66     nxt_java_start,
67 };
68 
69 typedef struct {
70     JavaVM               *jvm;
71     jobject              cl;
72     jobject              ctx;
73     nxt_java_app_conf_t  *conf;
74 } nxt_java_data_t;
75 
76 
77 static nxt_int_t
nxt_java_setup(nxt_task_t * task,nxt_process_t * process,nxt_common_app_conf_t * conf)78 nxt_java_setup(nxt_task_t *task, nxt_process_t *process,
79     nxt_common_app_conf_t *conf)
80 {
81     char        *path, *relpath, *p, *rootfs;
82     size_t      jars_dir_len, rootfs_len;
83     const char  *unit_jars;
84 
85     rootfs = (char *) process->isolation.rootfs;
86     rootfs_len = 0;
87 
88     unit_jars = conf->u.java.unit_jars;
89     if (unit_jars == NULL) {
90         if (rootfs != NULL) {
91             unit_jars = "/";
92         } else {
93             unit_jars = NXT_JARS;
94         }
95     }
96 
97     relpath = strdup(unit_jars);
98     if (nxt_slow_path(relpath == NULL)) {
99         return NXT_ERROR;
100     }
101 
102     if (rootfs != NULL) {
103         jars_dir_len = strlen(unit_jars);
104         rootfs_len = strlen(rootfs);
105 
106         path = nxt_malloc(jars_dir_len + rootfs_len + 1);
107         if (nxt_slow_path(path == NULL)) {
108             free(relpath);
109             return NXT_ERROR;
110         }
111 
112         p = nxt_cpymem(path, process->isolation.rootfs, rootfs_len);
113         p = nxt_cpymem(p, relpath, jars_dir_len);
114         *p = '\0';
115 
116         free(relpath);
117 
118     } else {
119         path = relpath;
120     }
121 
122     nxt_java_modules = realpath(path, NULL);
123     if (nxt_java_modules == NULL) {
124         nxt_alert(task, "realpath(\"%s\") failed %E", path, nxt_errno);
125         goto free;
126     }
127 
128     if (rootfs != NULL && strlen(path) > rootfs_len) {
129         nxt_java_modules = path + rootfs_len;
130     }
131 
132     nxt_debug(task, "JAVA MODULES: %s", nxt_java_modules);
133 
134     return NXT_OK;
135 
136 free:
137 
138     nxt_free(path);
139 
140     return NXT_ERROR;
141 }
142 
143 
144 static char **
nxt_java_module_jars(const char * jars[],int jar_count)145 nxt_java_module_jars(const char *jars[], int jar_count)
146 {
147     char        **res, *jurl;
148     uint8_t     pathsep;
149     nxt_int_t   modules_len, jlen, i;
150     const char  **jar;
151 
152     res = nxt_malloc(jar_count * sizeof(char*));
153     if (res == NULL) {
154         return NULL;
155     }
156 
157     modules_len = nxt_strlen(nxt_java_modules);
158 
159     pathsep = nxt_java_modules[modules_len - 1] == '/';
160 
161     for (i = 0, jar = jars; *jar != NULL; jar++) {
162         jlen = nxt_length("file:") + modules_len
163                + (!pathsep ? nxt_length("/") : 0)
164                + nxt_strlen(*jar) + 1;
165 
166         jurl = nxt_malloc(jlen);
167         if (jurl == NULL) {
168             return NULL;
169         }
170 
171         res[i++] = jurl;
172 
173         jurl = nxt_cpymem(jurl, "file:", nxt_length("file:"));
174         jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len);
175 
176         if (!pathsep) {
177             *jurl++ = '/';
178         }
179 
180         jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar));
181         *jurl++ = '\0';
182     }
183 
184     return res;
185 }
186 
187 
188 static nxt_int_t
nxt_java_start(nxt_task_t * task,nxt_process_data_t * data)189 nxt_java_start(nxt_task_t *task, nxt_process_data_t *data)
190 {
191     jint                   rc;
192     char                   *opt, *real_path;
193     char                   **classpath_arr, **unit_jars, **system_jars;
194     JavaVM                 *jvm;
195     JNIEnv                 *env;
196     jobject                cl, classpath;
197     nxt_str_t              str;
198     nxt_int_t              opt_len, real_path_len;
199     nxt_uint_t             i, unit_jars_count, classpath_count;
200     nxt_uint_t             system_jars_count;
201     JavaVMOption           *jvm_opt;
202     JavaVMInitArgs         jvm_args;
203     nxt_unit_ctx_t         *ctx;
204     nxt_unit_init_t        java_init;
205     nxt_java_data_t        java_data;
206     nxt_conf_value_t       *value;
207     nxt_java_app_conf_t    *c;
208     nxt_common_app_conf_t  *app_conf;
209 
210     //setenv("ASAN_OPTIONS", "handle_segv=0", 1);
211 
212     jvm_args.version = JNI_VERSION_1_6;
213     jvm_args.nOptions = 0;
214     jvm_args.ignoreUnrecognized = 0;
215 
216     app_conf = data->app;
217     c = &app_conf->u.java;
218 
219     if (c->options != NULL) {
220         jvm_args.nOptions += nxt_conf_array_elements_count(c->options);
221     }
222 
223     jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption));
224     if (jvm_opt == NULL) {
225         nxt_alert(task, "failed to allocate jvm_opt");
226         return NXT_ERROR;
227     }
228 
229     jvm_args.options = jvm_opt;
230 
231     unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1;
232 
233     unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count);
234     if (unit_jars == NULL) {
235         nxt_alert(task, "failed to allocate buffer for unit_jars array");
236 
237         return NXT_ERROR;
238     }
239 
240     system_jars_count = nxt_nitems(nxt_java_system_jars) - 1;
241 
242     system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count);
243     if (system_jars == NULL) {
244         nxt_alert(task, "failed to allocate buffer for system_jars array");
245 
246         return NXT_ERROR;
247     }
248 
249     if (c->options != NULL) {
250 
251         for (i = 0; /* void */ ; i++) {
252             value = nxt_conf_get_array_element(c->options, i);
253             if (value == NULL) {
254                 break;
255             }
256 
257             nxt_conf_get_string(value, &str);
258 
259             opt = nxt_malloc(str.length + 1);
260             if (opt == NULL) {
261                 nxt_alert(task, "failed to allocate jvm_opt");
262                 return NXT_ERROR;
263             }
264 
265             memcpy(opt, str.start, str.length);
266             opt[str.length] = '\0';
267 
268             jvm_opt[i].optionString = opt;
269         }
270     }
271 
272     if (c->classpath != NULL) {
273         classpath_count = nxt_conf_array_elements_count(c->classpath);
274         classpath_arr = nxt_malloc(classpath_count * sizeof(char *));
275 
276         for (i = 0; /* void */ ; i++) {
277             value = nxt_conf_get_array_element(c->classpath, i);
278             if (value == NULL) {
279                 break;
280             }
281 
282             nxt_conf_get_string(value, &str);
283 
284             opt_len = str.length + 1;
285 
286             char *sc = memchr(str.start, ':', str.length);
287             if (sc == NULL && str.start[0] == '/') {
288                 opt_len += nxt_length("file:");
289             }
290 
291             opt = nxt_malloc(opt_len);
292             if (opt == NULL) {
293                 nxt_alert(task, "failed to allocate classpath");
294                 return NXT_ERROR;
295             }
296 
297             if (sc == NULL && str.start[0] != '/') {
298                 nxt_memcpy(opt, str.start, str.length);
299                 opt[str.length] = '\0';
300 
301                 real_path = realpath(opt, NULL);
302                 if (real_path == NULL) {
303                     nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno);
304                     return NXT_ERROR;
305                 }
306 
307                 real_path_len = nxt_strlen(real_path);
308 
309                 free(opt);
310 
311                 opt_len = nxt_length("file:") + real_path_len + 1;
312 
313                 opt = nxt_malloc(opt_len);
314                 if (opt == NULL) {
315                     nxt_alert(task, "failed to allocate classpath");
316                     return NXT_ERROR;
317                 }
318 
319             } else {
320                 real_path = (char *) str.start;  /* I love this cast! */
321                 real_path_len = str.length;
322             }
323 
324             classpath_arr[i] = opt;
325 
326             if (sc == NULL) {
327                 opt = nxt_cpymem(opt, "file:", nxt_length("file:"));
328             }
329 
330             opt = nxt_cpymem(opt, real_path, real_path_len);
331             *opt = '\0';
332         }
333 
334     } else {
335         classpath_count = 0;
336         classpath_arr = NULL;
337     }
338 
339     rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args);
340     if (rc != JNI_OK) {
341         nxt_alert(task, "failed to create Java VM: %d", (int) rc);
342         return NXT_ERROR;
343     }
344 
345     rc = nxt_java_initThread(env);
346     if (rc != NXT_UNIT_OK) {
347         nxt_alert(task, "nxt_java_initThread() failed");
348         goto env_failed;
349     }
350 
351     rc = nxt_java_initURLClassLoader(env);
352     if (rc != NXT_UNIT_OK) {
353         nxt_alert(task, "nxt_java_initURLClassLoader() failed");
354         goto env_failed;
355     }
356 
357     cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars);
358     if (cl == NULL) {
359         nxt_alert(task, "nxt_java_newURLClassLoader failed");
360         goto env_failed;
361     }
362 
363     nxt_java_setContextClassLoader(env, cl);
364 
365     cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl);
366     if (cl == NULL) {
367         nxt_alert(task, "nxt_java_newURLClassLoader_parent failed");
368         goto env_failed;
369     }
370 
371     nxt_java_setContextClassLoader(env, cl);
372 
373     rc = nxt_java_initContext(env, cl);
374     if (rc != NXT_UNIT_OK) {
375         nxt_alert(task, "nxt_java_initContext() failed");
376         goto env_failed;
377     }
378 
379     rc = nxt_java_initRequest(env, cl);
380     if (rc != NXT_UNIT_OK) {
381         nxt_alert(task, "nxt_java_initRequest() failed");
382         goto env_failed;
383     }
384 
385     rc = nxt_java_initResponse(env, cl);
386     if (rc != NXT_UNIT_OK) {
387         nxt_alert(task, "nxt_java_initResponse() failed");
388         goto env_failed;
389     }
390 
391     rc = nxt_java_initInputStream(env, cl);
392     if (rc != NXT_UNIT_OK) {
393         nxt_alert(task, "nxt_java_initInputStream() failed");
394         goto env_failed;
395     }
396 
397     rc = nxt_java_initOutputStream(env, cl);
398     if (rc != NXT_UNIT_OK) {
399         nxt_alert(task, "nxt_java_initOutputStream() failed");
400         goto env_failed;
401     }
402 
403     nxt_java_jni_init(env);
404     if (rc != NXT_UNIT_OK) {
405         nxt_alert(task, "nxt_java_jni_init() failed");
406         goto env_failed;
407     }
408 
409     classpath = nxt_java_newURLs(env, classpath_count, classpath_arr);
410     if (classpath == NULL) {
411         nxt_alert(task, "nxt_java_newURLs failed");
412         goto env_failed;
413     }
414 
415     java_data.jvm = jvm;
416     java_data.cl = cl;
417     java_data.ctx = nxt_java_startContext(env, c->webapp, classpath);
418     java_data.conf = c;
419 
420     if ((*env)->ExceptionCheck(env)) {
421         nxt_alert(task, "Unhandled exception in application start");
422         (*env)->ExceptionDescribe(env);
423         return NXT_ERROR;
424     }
425 
426     rc = nxt_java_init_threads(c);
427     if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
428         return NXT_ERROR;
429     }
430 
431     nxt_unit_default_init(task, &java_init, app_conf);
432 
433     java_init.callbacks.request_handler = nxt_java_request_handler;
434     java_init.callbacks.websocket_handler = nxt_java_websocket_handler;
435     java_init.callbacks.close_handler = nxt_java_close_handler;
436     java_init.callbacks.ready_handler = nxt_java_ready_handler;
437     java_init.request_data_size = sizeof(nxt_java_request_data_t);
438     java_init.data = &java_data;
439     java_init.ctx_data = env;
440 
441     ctx = nxt_unit_init(&java_init);
442     if (nxt_slow_path(ctx == NULL)) {
443         nxt_alert(task, "nxt_unit_init() failed");
444         return NXT_ERROR;
445     }
446 
447     rc = nxt_unit_run(ctx);
448 
449     nxt_java_join_threads(ctx, c);
450 
451     nxt_java_stopContext(env, java_data.ctx);
452 
453     if ((*env)->ExceptionCheck(env)) {
454         (*env)->ExceptionDescribe(env);
455     }
456 
457     nxt_unit_done(ctx);
458 
459     (*jvm)->DestroyJavaVM(jvm);
460 
461     exit(rc);
462 
463     return NXT_OK;
464 
465 env_failed:
466 
467     if ((*env)->ExceptionCheck(env)) {
468         (*env)->ExceptionDescribe(env);
469     }
470 
471     return NXT_ERROR;
472 }
473 
474 
475 static void
nxt_java_request_handler(nxt_unit_request_info_t * req)476 nxt_java_request_handler(nxt_unit_request_info_t *req)
477 {
478     JNIEnv                   *env;
479     jobject                  jreq, jresp;
480     nxt_java_data_t          *java_data;
481     nxt_java_request_data_t  *data;
482 
483     java_data = req->unit->data;
484     env = req->ctx->data;
485     data = req->data;
486 
487     jreq = nxt_java_newRequest(env, java_data->ctx, req);
488     if (jreq == NULL) {
489         nxt_unit_req_alert(req, "failed to create Request instance");
490 
491         if ((*env)->ExceptionCheck(env)) {
492             (*env)->ExceptionDescribe(env);
493             (*env)->ExceptionClear(env);
494         }
495 
496         nxt_unit_request_done(req, NXT_UNIT_ERROR);
497         return;
498     }
499 
500     jresp = nxt_java_newResponse(env, req);
501     if (jresp == NULL) {
502         nxt_unit_req_alert(req, "failed to create Response instance");
503 
504         if ((*env)->ExceptionCheck(env)) {
505             (*env)->ExceptionDescribe(env);
506             (*env)->ExceptionClear(env);
507         }
508 
509         (*env)->DeleteLocalRef(env, jreq);
510 
511         nxt_unit_request_done(req, NXT_UNIT_ERROR);
512         return;
513     }
514 
515     data->header_size = 10 * 1024;
516     data->buf_size = 32 * 1024; /* from Jetty */
517     data->jreq = jreq;
518     data->jresp = jresp;
519     data->buf = NULL;
520 
521     nxt_unit_request_group_dup_fields(req);
522 
523     nxt_java_service(env, java_data->ctx, jreq, jresp);
524 
525     if ((*env)->ExceptionCheck(env)) {
526         (*env)->ExceptionDescribe(env);
527         (*env)->ExceptionClear(env);
528     }
529 
530     if (!nxt_unit_response_is_init(req)) {
531         nxt_unit_response_init(req, 200, 0, 0);
532     }
533 
534     if (!nxt_unit_response_is_sent(req)) {
535         nxt_unit_response_send(req);
536     }
537 
538     if (data->buf != NULL) {
539         nxt_unit_buf_send(data->buf);
540 
541         data->buf = NULL;
542     }
543 
544     if (nxt_unit_response_is_websocket(req)) {
545         data->jreq = (*env)->NewGlobalRef(env, jreq);
546         data->jresp = (*env)->NewGlobalRef(env, jresp);
547 
548     } else {
549         nxt_unit_request_done(req, NXT_UNIT_OK);
550     }
551 
552     (*env)->DeleteLocalRef(env, jresp);
553     (*env)->DeleteLocalRef(env, jreq);
554 }
555 
556 
557 static void
nxt_java_websocket_handler(nxt_unit_websocket_frame_t * ws)558 nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws)
559 {
560     void                     *b;
561     JNIEnv                   *env;
562     jobject                  jbuf;
563     nxt_java_request_data_t  *data;
564 
565     env = ws->req->ctx->data;
566     data = ws->req->data;
567 
568     b = malloc(ws->payload_len);
569     if (b != NULL) {
570         nxt_unit_websocket_read(ws, b, ws->payload_len);
571 
572         jbuf = (*env)->NewDirectByteBuffer(env, b, ws->payload_len);
573         if (jbuf != NULL) {
574             nxt_java_Request_websocket(env, data->jreq, jbuf,
575                                        ws->header->opcode, ws->header->fin);
576 
577             if ((*env)->ExceptionCheck(env)) {
578                 (*env)->ExceptionDescribe(env);
579                 (*env)->ExceptionClear(env);
580             }
581 
582             (*env)->DeleteLocalRef(env, jbuf);
583         }
584 
585         free(b);
586     }
587 
588     nxt_unit_websocket_done(ws);
589 }
590 
591 
592 static void
nxt_java_close_handler(nxt_unit_request_info_t * req)593 nxt_java_close_handler(nxt_unit_request_info_t *req)
594 {
595     JNIEnv                   *env;
596     nxt_java_request_data_t  *data;
597 
598     env = req->ctx->data;
599     data = req->data;
600 
601     nxt_java_Request_close(env, data->jreq);
602 
603     (*env)->DeleteGlobalRef(env, data->jresp);
604     (*env)->DeleteGlobalRef(env, data->jreq);
605 
606     nxt_unit_request_done(req, NXT_UNIT_OK);
607 }
608 
609 
610 static int
nxt_java_ready_handler(nxt_unit_ctx_t * ctx)611 nxt_java_ready_handler(nxt_unit_ctx_t *ctx)
612 {
613     int                  res;
614     uint32_t             i;
615     nxt_java_data_t      *java_data;
616     nxt_java_app_conf_t  *c;
617 
618     java_data = ctx->unit->data;
619     c = java_data->conf;
620 
621     if (c->threads <= 1) {
622         return NXT_UNIT_OK;
623     }
624 
625     for (i = 0; i < c->threads - 1; i++) {
626         res = pthread_create(&nxt_java_threads[i], nxt_java_thread_attr,
627                              nxt_java_thread_func, ctx);
628 
629         if (nxt_fast_path(res == 0)) {
630             nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
631 
632         } else {
633             nxt_unit_alert(ctx, "thread #%d create failed: %s (%d)",
634                            (int) (i + 1), strerror(res), res);
635 
636             return NXT_UNIT_ERROR;
637         }
638     }
639 
640     return NXT_UNIT_OK;
641 }
642 
643 
644 static void *
nxt_java_thread_func(void * data)645 nxt_java_thread_func(void *data)
646 {
647     int              rc;
648     JavaVM           *jvm;
649     JNIEnv           *env;
650     nxt_unit_ctx_t   *main_ctx, *ctx;
651     nxt_java_data_t  *java_data;
652 
653     main_ctx = data;
654 
655     nxt_unit_debug(main_ctx, "worker thread start");
656 
657     java_data = main_ctx->unit->data;
658     jvm = java_data->jvm;
659 
660     rc = (*jvm)->AttachCurrentThread(jvm, (void **) &env, NULL);
661     if (rc != JNI_OK) {
662         nxt_unit_alert(main_ctx, "failed to attach Java VM: %d", (int) rc);
663         return NULL;
664     }
665 
666     nxt_java_setContextClassLoader(env, java_data->cl);
667 
668     ctx = nxt_unit_ctx_alloc(main_ctx, env);
669     if (nxt_slow_path(ctx == NULL)) {
670         goto fail;
671     }
672 
673     (void) nxt_unit_run(ctx);
674 
675     nxt_unit_done(ctx);
676 
677 fail:
678 
679     (*jvm)->DetachCurrentThread(jvm);
680 
681     nxt_unit_debug(NULL, "worker thread end");
682 
683     return NULL;
684 }
685 
686 
687 static int
nxt_java_init_threads(nxt_java_app_conf_t * c)688 nxt_java_init_threads(nxt_java_app_conf_t *c)
689 {
690     int                    res;
691     static pthread_attr_t  attr;
692 
693     if (c->threads <= 1) {
694         return NXT_UNIT_OK;
695     }
696 
697     if (c->thread_stack_size > 0) {
698         res = pthread_attr_init(&attr);
699         if (nxt_slow_path(res != 0)) {
700             nxt_unit_alert(NULL, "thread attr init failed: %s (%d)",
701                            strerror(res), res);
702 
703             return NXT_UNIT_ERROR;
704         }
705 
706         res = pthread_attr_setstacksize(&attr, c->thread_stack_size);
707         if (nxt_slow_path(res != 0)) {
708             nxt_unit_alert(NULL, "thread attr set stack size failed: %s (%d)",
709                            strerror(res), res);
710 
711             return NXT_UNIT_ERROR;
712         }
713 
714         nxt_java_thread_attr = &attr;
715     }
716 
717     nxt_java_threads = nxt_unit_malloc(NULL,
718                                        sizeof(pthread_t) * (c->threads - 1));
719     if (nxt_slow_path(nxt_java_threads == NULL)) {
720         nxt_unit_alert(NULL, "Failed to allocate thread id array");
721 
722         return NXT_UNIT_ERROR;
723     }
724 
725     memset(nxt_java_threads, 0, sizeof(pthread_t) * (c->threads - 1));
726 
727     return NXT_UNIT_OK;
728 }
729 
730 
731 static void
nxt_java_join_threads(nxt_unit_ctx_t * ctx,nxt_java_app_conf_t * c)732 nxt_java_join_threads(nxt_unit_ctx_t *ctx, nxt_java_app_conf_t *c)
733 {
734     int       res;
735     uint32_t  i;
736 
737     if (nxt_java_threads == NULL) {
738         return;
739     }
740 
741     for (i = 0; i < c->threads - 1; i++) {
742         if ((uintptr_t) nxt_java_threads[i] == 0) {
743             continue;
744         }
745 
746         res = pthread_join(nxt_java_threads[i], NULL);
747 
748         if (nxt_fast_path(res == 0)) {
749             nxt_unit_debug(ctx, "thread #%d joined", (int) i);
750 
751         } else {
752             nxt_unit_alert(ctx, "thread #%d join failed: %s (%d)",
753                            (int) i, strerror(res), res);
754         }
755     }
756 
757     nxt_unit_free(ctx, nxt_java_threads);
758 }
759 
760 
761