xref: /illumos-gate/usr/src/cmd/pools/poold/poold.c (revision f00e6aa6)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * poold - dynamically adjust pool configuration according to load.
31  */
32 #include <errno.h>
33 #include <jni.h>
34 #include <libintl.h>
35 #include <limits.h>
36 #include <link.h>
37 #include <locale.h>
38 #include <poll.h>
39 #include <pool.h>
40 #include <priv.h>
41 #include <pthread.h>
42 #include <signal.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
48 
49 #include <sys/stat.h>
50 #include <sys/types.h>
51 #include <sys/ucontext.h>
52 #include "utils.h"
53 
54 #define	POOLD_DEF_CLASSPATH	"/usr/lib/pool/JPool.jar"
55 #define	POOLD_DEF_LIBPATH	"/usr/lib/pool"
56 #define	SMF_SVC_INSTANCE	"svc:/system/pools/dynamic:default"
57 
58 #if defined(sparc)
59 #define	PLAT	"sparc"
60 #else
61 #if defined(i386)
62 #define	PLAT	"i386"
63 #else
64 #error Unrecognized platform.
65 #endif
66 #endif
67 
68 #define	CLASS_FIELD_DESC(class_desc)	"L" class_desc ";"
69 
70 #define	LEVEL_CLASS_DESC	"java/util/logging/Level"
71 #define	POOLD_CLASS_DESC	"com/sun/solaris/domain/pools/Poold"
72 #define	SEVERITY_CLASS_DESC	"com/sun/solaris/service/logging/Severity"
73 #define	STRING_CLASS_DESC	"java/lang/String"
74 #define	SYSTEM_CLASS_DESC	"java/lang/System"
75 #define	LOGGER_CLASS_DESC	"java/util/logging/Logger"
76 
77 extern char *optarg;
78 
79 static const char *pname;
80 
81 static enum {
82 	LD_TERMINAL = 1,
83 	LD_SYSLOG,
84 	LD_JAVA
85 } log_dest = LD_SYSLOG;
86 
87 static const char PNAME_FMT[] = "%s: ";
88 static const char ERRNO_FMT[] = ": %s";
89 
90 static pthread_mutex_t jvm_lock = PTHREAD_MUTEX_INITIALIZER;
91 static JavaVM *jvm;		/* protected by jvm_lock */
92 static int instance_running;	/* protected by jvm_lock */
93 static int lflag;		/* specifies poold logging mode */
94 
95 static jmethodID log_mid;
96 static jobject severity_err;
97 static jobject severity_notice;
98 static jobject base_log;
99 static jclass poold_class;
100 static jobject poold_instance;
101 
102 static sigset_t hdl_set;
103 
104 static void pu_notice(const char *fmt, ...);
105 static void pu_die(const char *fmt, ...) __NORETURN;
106 
107 static void
108 usage(void)
109 {
110 	(void) fprintf(stderr, gettext("Usage:\t%s [-l <level>]\n"), pname);
111 
112 	exit(E_USAGE);
113 }
114 
115 static void
116 check_thread_attached(JNIEnv **env)
117 {
118 	int ret;
119 
120 	ret = (*jvm)->GetEnv(jvm, (void **)env, JNI_VERSION_1_4);
121 	if (*env == NULL) {
122 		if (ret == JNI_EVERSION) {
123 			/*
124 			 * Avoid recursively calling
125 			 * check_thread_attached()
126 			 */
127 			if (log_dest == LD_JAVA)
128 				log_dest = LD_TERMINAL;
129 			pu_notice(gettext("incorrect JNI version"));
130 			exit(E_ERROR);
131 		}
132 		if ((*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)env,
133 			NULL) != 0) {
134 			/*
135 			 * Avoid recursively calling
136 			 * check_thread_attached()
137 			 */
138 			if (log_dest == LD_JAVA)
139 				log_dest = LD_TERMINAL;
140 			pu_notice(gettext("thread attach failed"));
141 			exit(E_ERROR);
142 		}
143 	}
144 }
145 
146 /*
147  * Output a message to the designated logging destination.
148  *
149  * severity - Specified the severity level when using LD_JAVA logging
150  * fmt - specified the format of the output message
151  * alist - varargs used in the output message
152  */
153 static void
154 pu_output(int severity, const char *fmt, va_list alist)
155 {
156 	int err = errno;
157 	char line[255] = "";
158 	jobject jseverity;
159 	jobject jline;
160 	JNIEnv *env = NULL;
161 
162 	if (pname != NULL && log_dest == LD_TERMINAL)
163 		(void) snprintf(line, sizeof (line), gettext(PNAME_FMT), pname);
164 
165 	(void) vsnprintf(line + strlen(line), sizeof (line) - strlen(line),
166 	    fmt, alist);
167 
168 	if (line[strlen(line) - 1] != '\n')
169 		(void) snprintf(line + strlen(line), sizeof (line) -
170 		    strlen(line), gettext(ERRNO_FMT), strerror(err));
171 	else
172 		line[strlen(line) - 1] = 0;
173 
174 	switch (log_dest) {
175 	case LD_TERMINAL:
176 		(void) fprintf(stderr, "%s\n", line);
177 		(void) fflush(stderr);
178 		break;
179 	case LD_SYSLOG:
180 		syslog(LOG_ERR, "%s", line);
181 		break;
182 	case LD_JAVA:
183 		if (severity == LOG_ERR)
184 			jseverity = severity_err;
185 		else
186 			jseverity = severity_notice;
187 
188 		if (jvm) {
189 			check_thread_attached(&env);
190 			if ((jline = (*env)->NewStringUTF(env, line)) != NULL)
191 				(*env)->CallVoidMethod(env, base_log, log_mid,
192 				    jseverity, jline);
193 		}
194 	}
195 }
196 
197 /*
198  * Notify the user with the supplied message.
199  */
200 /*PRINTFLIKE1*/
201 static void
202 pu_notice(const char *fmt, ...)
203 {
204 	va_list alist;
205 
206 	va_start(alist, fmt);
207 	pu_output(LOG_NOTICE, fmt, alist);
208 	va_end(alist);
209 }
210 
211 /*
212  * Stop the application executing inside the JVM. Always ensure that jvm_lock
213  * is held before invoking this function.
214  */
215 static void
216 halt_application(void)
217 {
218 	JNIEnv *env = NULL;
219 	jmethodID poold_shutdown_mid;
220 
221 	if (jvm && instance_running) {
222 		check_thread_attached(&env);
223 		if ((poold_shutdown_mid = (*env)->GetMethodID(
224 		    env, poold_class, "shutdown", "()V")) != NULL) {
225 			(*env)->CallVoidMethod(env, poold_instance,
226 			    poold_shutdown_mid);
227 		} else {
228 			if (lflag && (*env)->ExceptionOccurred(env)) {
229 				(*env)->ExceptionDescribe(env);
230 				pu_notice("could not invoke proper shutdown\n");
231 			}
232 		}
233 		instance_running = 0;
234 	}
235 }
236 
237 /*
238  * Warn the user with the supplied error message, halt the application,
239  * destroy the JVM and then exit the process.
240  */
241 /*PRINTFLIKE1*/
242 static void
243 pu_die(const char *fmt, ...)
244 {
245 	va_list alist;
246 
247 	va_start(alist, fmt);
248 	pu_output(LOG_ERR, fmt, alist);
249 	va_end(alist);
250 	halt_application();
251 	if (jvm) {
252 		(*jvm)->DestroyJavaVM(jvm);
253 		jvm = NULL;
254 	}
255 	exit(E_ERROR);
256 }
257 
258 /*
259  * Warn the user with the supplied error message and halt the
260  * application. This function is very similar to pu_die(). However,
261  * this function is designed to be called from the signal handling
262  * routine (handle_sig()) where although we wish to let the user know
263  * that an error has occurred, we do not wish to destroy the JVM or
264  * exit the process.
265  */
266 /*PRINTFLIKE1*/
267 static void
268 pu_terminate(const char *fmt, ...)
269 {
270 	va_list alist;
271 
272 	va_start(alist, fmt);
273 	pu_output(LOG_ERR, fmt, alist);
274 	va_end(alist);
275 	halt_application();
276 }
277 
278 /*
279  * If SIGHUP is invoked, we should just re-initialize poold. Since
280  * there is no easy way to determine when it's safe to re-initialzie
281  * poold, simply update a dummy property on the system element to
282  * force pool_conf_update() to detect a change.
283  *
284  * Both SIGTERM and SIGINT are interpreted as instructions to
285  * shutdown.
286  */
287 /*ARGSUSED*/
288 static void *
289 handle_sig(void *arg)
290 {
291 	pool_conf_t *conf = NULL;
292 	pool_elem_t *pe;
293 	pool_value_t *val;
294 	const char *err_desc;
295 	int keep_handling = 1;
296 
297 	while (keep_handling) {
298 		int sig;
299 		char buf[SIG2STR_MAX];
300 
301 		if ((sig = sigwait(&hdl_set)) < 0) {
302 			/*
303 			 * We used forkall() previously to ensure that
304 			 * all threads started by the JVM are
305 			 * duplicated in the child. Since forkall()
306 			 * can cause blocking system calls to be
307 			 * interrupted, check to see if the errno is
308 			 * EINTR and if it is wait again.
309 			 */
310 			if (errno == EINTR)
311 				continue;
312 			(void) pthread_mutex_lock(&jvm_lock);
313 			pu_terminate("unexpected error: %d\n", errno);
314 			keep_handling = 0;
315 		} else
316 			(void) pthread_mutex_lock(&jvm_lock);
317 		(void) sig2str(sig, buf);
318 		switch (sig) {
319 		case SIGHUP:
320 			if ((conf = pool_conf_alloc()) == NULL) {
321 				err_desc = pool_strerror(pool_error());
322 				goto destroy;
323 			}
324 			if (pool_conf_open(conf, pool_dynamic_location(),
325 			    PO_RDWR) != 0) {
326 				err_desc = pool_strerror(pool_error());
327 				goto destroy;
328 			}
329 
330 			if ((val = pool_value_alloc()) == NULL) {
331 				err_desc = pool_strerror(pool_error());
332 				goto destroy;
333 			}
334 			pe = pool_conf_to_elem(conf);
335 			pool_value_set_bool(val, 1);
336 			if (pool_put_property(conf, pe, "system.poold.sighup",
337 			    val) != PO_SUCCESS) {
338 				err_desc = pool_strerror(pool_error());
339 				pool_value_free(val);
340 				goto destroy;
341 			}
342 			pool_value_free(val);
343 			(void) pool_rm_property(conf, pe,
344 			    "system.poold.sighup");
345 			if (pool_conf_commit(conf, 0) != PO_SUCCESS) {
346 				err_desc = pool_strerror(pool_error());
347 				goto destroy;
348 			}
349 			break;
350 destroy:
351 			if (conf) {
352 				(void) pool_conf_close(conf);
353 				pool_conf_free(conf);
354 			}
355 			pu_terminate(err_desc);
356 			keep_handling = 0;
357 			break;
358 		case SIGINT:
359 		case SIGTERM:
360 		default:
361 			pu_terminate("terminating due to signal: SIG%s\n", buf);
362 			keep_handling = 0;
363 			break;
364 		}
365 		(void) pthread_mutex_unlock(&jvm_lock);
366 	}
367 	pthread_exit(NULL);
368 	/*NOTREACHED*/
369 	return (NULL);
370 }
371 
372 /*
373  * Return the name of the process
374  */
375 static const char *
376 pu_getpname(const char *arg0)
377 {
378 	char *p;
379 
380 	/*
381 	 * Guard against '/' at end of command invocation.
382 	 */
383 	for (;;) {
384 		p = strrchr(arg0, '/');
385 		if (p == NULL) {
386 			pname = arg0;
387 			break;
388 		} else {
389 			if (*(p + 1) == '\0') {
390 				*p = '\0';
391 				continue;
392 			}
393 
394 			pname = p + 1;
395 			break;
396 		}
397 	}
398 
399 	return (pname);
400 }
401 
402 int
403 main(int argc, char *argv[])
404 {
405 	char c;
406 	char log_severity[16] = "";
407 	JavaVMInitArgs vm_args;
408 	JavaVMOption vm_opts[5];
409 	int nopts = 0;
410 	const char *classpath;
411 	const char *libpath;
412 	size_t len;
413 	const char *err_desc;
414 	JNIEnv *env;
415 	jmethodID poold_getinstancewcl_mid;
416 	jmethodID poold_run_mid;
417 	jobject log_severity_string = NULL;
418 	jobject log_severity_obj = NULL;
419 	jclass severity_class;
420 	jmethodID severity_cons_mid;
421 	jfieldID base_log_fid;
422 	pthread_t hdl_thread;
423 	FILE *p;
424 
425 	(void) pthread_mutex_lock(&jvm_lock);
426 	pname = pu_getpname(argv[0]);
427 	openlog(pname, 0, LOG_DAEMON);
428 	(void) chdir("/");
429 
430 	(void) setlocale(LC_ALL, "");
431 #if !defined(TEXT_DOMAIN)		/* Should be defined with cc -D. */
432 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it wasn't. */
433 #endif
434 	(void) textdomain(TEXT_DOMAIN);
435 
436 	opterr = 0;
437 	while ((c = getopt(argc, argv, "l:P")) != EOF) {
438 		switch (c) {
439 		case 'l':	/* -l option */
440 			lflag++;
441 			(void) strlcpy(log_severity, optarg,
442 			    sizeof (log_severity));
443 			log_dest = LD_TERMINAL;
444 			break;
445 		default:
446 			usage();
447 			/*NOTREACHED*/
448 		}
449 	}
450 
451 	/*
452 	 * Check permission
453 	 */
454 	if (!priv_ineffect(PRIV_SYS_RES_CONFIG))
455 		pu_die(gettext(ERR_PRIVILEGE), PRIV_SYS_RES_CONFIG);
456 
457 	/*
458 	 * In order to avoid problems with arbitrary thread selection
459 	 * when handling asynchronous signals, dedicate a thread to
460 	 * look after these signals.
461 	 */
462 	if (sigemptyset(&hdl_set) < 0 ||
463 	    sigaddset(&hdl_set, SIGHUP) < 0 ||
464 	    sigaddset(&hdl_set, SIGTERM) < 0 ||
465 	    sigaddset(&hdl_set, SIGINT) < 0 ||
466 	    pthread_sigmask(SIG_BLOCK, &hdl_set, NULL) ||
467 	    pthread_create(&hdl_thread, NULL, handle_sig, NULL))
468 		pu_die(gettext("can't install signal handler"));
469 
470 	/*
471 	 * If the -l flag is supplied, terminate the SMF service and
472 	 * run interactively from the command line.
473 	 */
474 	if (lflag) {
475 		char *cmd = "/usr/sbin/svcadm disable -st " SMF_SVC_INSTANCE;
476 
477 		if (getenv("SMF_FMRI") != NULL)
478 			pu_die("-l option illegal: %s\n", SMF_SVC_INSTANCE);
479 		/*
480 		 * Since disabling a service isn't synchronous, use the
481 		 * synchronous option from svcadm to achieve synchronous
482 		 * behaviour.
483 		 * This is not very satisfactory, but since this is only
484 		 * for use in debugging scenarios, it will do until there
485 		 * is a C API to synchronously shutdown a service in SMF.
486 		 */
487 		if ((p = popen(cmd, "w")) == NULL || pclose(p) != 0)
488 			pu_die("could not temporarily disable service: %s\n",
489 			    SMF_SVC_INSTANCE);
490 	} else {
491 		/*
492 		 * Check if we are running as a SMF service. If we
493 		 * aren't, terminate this process after enabling the
494 		 * service.
495 		 */
496 		if (getenv("SMF_FMRI") == NULL) {
497 			char *cmd = "/usr/sbin/svcadm enable -s " \
498 			    SMF_SVC_INSTANCE;
499 			if ((p = popen(cmd, "w")) == NULL || pclose(p) != 0)
500 				pu_die("could not enable "
501 				    "service: %s\n", SMF_SVC_INSTANCE);
502 			return (E_PO_SUCCESS);
503 		}
504 	}
505 
506 	/*
507 	 * Establish the classpath and LD_LIBRARY_PATH for native
508 	 * methods, and get the interpreter going.
509 	 */
510 	if ((classpath = getenv("POOLD_CLASSPATH")) == NULL) {
511 		classpath = POOLD_DEF_CLASSPATH;
512 	} else {
513 		const char *cur = classpath;
514 
515 		/*
516 		 * Check the components to make sure they're absolute
517 		 * paths.
518 		 */
519 		while (cur != NULL && *cur) {
520 			if (*cur != '/')
521 				pu_die(gettext(
522 				    "POOLD_CLASSPATH must contain absolute "
523 				    "components\n"));
524 			cur = strchr(cur + 1, ':');
525 		}
526 	}
527 	vm_opts[nopts].optionString = malloc(len = strlen(classpath) +
528 	    strlen("-Djava.class.path=") + 1);
529 	(void) strlcpy(vm_opts[nopts].optionString, "-Djava.class.path=", len);
530 	(void) strlcat(vm_opts[nopts++].optionString, classpath, len);
531 
532 	if ((libpath = getenv("POOLD_LD_LIBRARY_PATH")) == NULL)
533 		libpath = POOLD_DEF_LIBPATH;
534 	vm_opts[nopts].optionString = malloc(len = strlen(libpath) +
535 	    strlen("-Djava.library.path=") + 1);
536 	(void) strlcpy(vm_opts[nopts].optionString, "-Djava.library.path=",
537 	    len);
538 	(void) strlcat(vm_opts[nopts++].optionString, libpath, len);
539 
540 	vm_opts[nopts++].optionString = "-Xrs";
541 	vm_opts[nopts++].optionString = "-enableassertions";
542 
543 	vm_args.options = vm_opts;
544 	vm_args.nOptions = nopts;
545 	vm_args.ignoreUnrecognized = JNI_FALSE;
546 	vm_args.version = 0x00010002;
547 
548 	if (JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args) < 0)
549 		pu_die(gettext("can't create Java VM"));
550 
551 	/*
552 	 * Locate the Poold class and construct an instance.  A side
553 	 * effect of this is that the poold instance's logHelper will be
554 	 * initialized, establishing loggers for logging errors from
555 	 * this point on.  (Note, in the event of an unanticipated
556 	 * exception, poold will invoke die() itself.)
557 	 */
558 	err_desc = gettext("JVM-related error initializing poold\n");
559 	if ((poold_class = (*env)->FindClass(env, POOLD_CLASS_DESC)) == NULL)
560 		goto destroy;
561 	if ((poold_getinstancewcl_mid = (*env)->GetStaticMethodID(env,
562 	    poold_class, "getInstanceWithConsoleLogging", "("
563 	    CLASS_FIELD_DESC(SEVERITY_CLASS_DESC) ")"
564 	    CLASS_FIELD_DESC(POOLD_CLASS_DESC))) == NULL)
565 		goto destroy;
566 	if ((poold_run_mid = (*env)->GetMethodID(env, poold_class, "run",
567 	    "()V")) == NULL)
568 		goto destroy;
569 	if ((severity_class = (*env)->FindClass(env, SEVERITY_CLASS_DESC))
570 	    == NULL)
571 		goto destroy;
572 	if ((severity_cons_mid = (*env)->GetStaticMethodID(env, severity_class,
573 	    "getSeverityWithName", "(" CLASS_FIELD_DESC(STRING_CLASS_DESC) ")"
574 	    CLASS_FIELD_DESC(SEVERITY_CLASS_DESC))) == NULL)
575 		goto destroy;
576 
577 	/*
578 	 * -l <level> was specified, indicating that messages are to be
579 	 * logged to the console only.
580 	 */
581 	if (strlen(log_severity) > 0) {
582 		if ((log_severity_string = (*env)->NewStringUTF(env,
583 		    log_severity)) == NULL)
584 			goto destroy;
585 		if ((log_severity_obj = (*env)->CallStaticObjectMethod(env,
586 		    severity_class, severity_cons_mid, log_severity_string)) ==
587 		    NULL) {
588 			err_desc = gettext("invalid level specified\n");
589 			goto destroy;
590 		}
591 	} else
592 		log_severity_obj = NULL;
593 
594 	if ((poold_instance = (*env)->CallStaticObjectMethod(env, poold_class,
595 	    poold_getinstancewcl_mid, log_severity_obj)) == NULL)
596 		goto destroy;
597 
598 	/*
599 	 * Grab a global reference to poold for use in our signal
600 	 * handlers.
601 	 */
602 	poold_instance = (*env)->NewGlobalRef(env, poold_instance);
603 
604 	/*
605 	 * Ready LD_JAVA logging.
606 	 */
607 	err_desc = gettext("cannot initialize logging\n");
608 	if ((log_severity_string = (*env)->NewStringUTF(env, "err")) == NULL)
609 		goto destroy;
610 	if (!(severity_err = (*env)->CallStaticObjectMethod(env, severity_class,
611 	    severity_cons_mid, log_severity_string)))
612 		goto destroy;
613 	if (!(severity_err = (*env)->NewGlobalRef(env, severity_err)))
614 		goto destroy;
615 
616 	if ((log_severity_string = (*env)->NewStringUTF(env, "notice")) == NULL)
617 		goto destroy;
618 	if (!(severity_notice = (*env)->CallStaticObjectMethod(env,
619 	    severity_class, severity_cons_mid, log_severity_string)))
620 		goto destroy;
621 	if (!(severity_notice = (*env)->NewGlobalRef(env, severity_notice)))
622 		goto destroy;
623 
624 	if (!(base_log_fid = (*env)->GetStaticFieldID(env, poold_class,
625 	    "BASE_LOG", CLASS_FIELD_DESC(LOGGER_CLASS_DESC))))
626 		goto destroy;
627 	if (!(base_log = (*env)->GetStaticObjectField(env, poold_class,
628 	    base_log_fid)))
629 		goto destroy;
630 	if (!(base_log = (*env)->NewGlobalRef(env, base_log)))
631 		goto destroy;
632 	if (!(log_mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env,
633 	    base_log), "log", "(" CLASS_FIELD_DESC(LEVEL_CLASS_DESC)
634 	    CLASS_FIELD_DESC(STRING_CLASS_DESC) ")V")))
635 		goto destroy;
636 	log_dest = LD_JAVA;
637 
638 	/*
639 	 * If invoked directly and -l is specified, forking is not
640 	 * desired.
641 	 */
642 	if (!lflag)
643 		switch (forkall()) {
644 		case 0:
645 			(void) setsid();
646 			(void) fclose(stdin);
647 			(void) fclose(stdout);
648 			(void) fclose(stderr);
649 			break;
650 		case -1:
651 			pu_die(gettext("cannot fork"));
652 			/*NOTREACHED*/
653 		default:
654 			return (E_PO_SUCCESS);
655 		}
656 
657 	instance_running = 1;
658 	(void) pthread_mutex_unlock(&jvm_lock);
659 
660 	(*env)->CallVoidMethod(env, poold_instance, poold_run_mid);
661 
662 	(void) pthread_mutex_lock(&jvm_lock);
663 	if ((*env)->ExceptionOccurred(env)) {
664 		goto destroy;
665 	}
666 	if (jvm) {
667 		(*jvm)->DestroyJavaVM(jvm);
668 		jvm = NULL;
669 	}
670 	(void) pthread_mutex_unlock(&jvm_lock);
671 	return (E_PO_SUCCESS);
672 
673 destroy:
674 	if (lflag && (*env)->ExceptionOccurred(env))
675 		(*env)->ExceptionDescribe(env);
676 	pu_die(err_desc);
677 }
678