3535
3636import static com .teamscale .jacoco .agent .logging .LoggingUtils .getLoggerContext ;
3737
38- /** Container class for the premain entry point for the agent. */
38+ /**
39+ * Container class for the premain entry point for the agent.
40+ * <p>
41+ * <b>Design decision: the agent must never crash the profiled application.</b>
42+ * Even when the agent is misconfigured, startup must not propagate any exception out of
43+ * {@link #premain(String, Instrumentation)}. An exception escaping {@code premain} would be re-thrown by the JVM as
44+ * {@code IllegalAgentException}, which aborts startup of the profiled application. The profiler is an observational
45+ * tool, so a failure to start up the profiler must degrade to "no coverage is collected" rather than taking down the
46+ * application. All failure paths in {@code premain} are therefore logged and swallowed; see the top-level
47+ * {@code try}/{@code catch} in {@link #premain(String, Instrumentation)}.
48+ */
3949public class PreMain {
4050
4151 private static LoggingUtils .LoggingResources loggingResources = null ;
@@ -59,11 +69,27 @@ public class PreMain {
5969 private static final String ACCESS_TOKEN_ENVIRONMENT_VARIABLE = "TEAMSCALE_ACCESS_TOKEN" ;
6070
6171 /**
62- * Entry point for the agent, called by the JVM. If this method throws an exception, the JVM will abort the
63- * entire application. Therefore, configuration errors must be handled gracefully by returning normally, which
64- * allows the application to start without coverage collection.
72+ * Entry point for the agent, called by the JVM.
73+ * <p>
74+ * This method never throws. Any exception raised during startup is logged (to the file log and, if configured,
75+ * to Teamscale) and swallowed, and the JVM continues without coverage collection. See the class-level Javadoc for
76+ * the rationale.
6577 */
66- public static void premain (String options , Instrumentation instrumentation ) throws Exception {
78+ public static void premain (String options , Instrumentation instrumentation ) {
79+ try {
80+ startProfiler (options , instrumentation );
81+ } catch (Throwable t ) {
82+ // Top-level safety net: an exception escaping premain would be re-thrown by the JVM as
83+ // IllegalAgentException and abort the profiled application. See class-level Javadoc.
84+ logStartupFailure (t );
85+ }
86+ }
87+
88+ /**
89+ * Starts the profiler. Contains all logic that may legitimately throw during startup; every caller must route
90+ * through {@link #premain(String, Instrumentation)} which catches these exceptions.
91+ */
92+ private static void startProfiler (String options , Instrumentation instrumentation ) throws Exception {
6793 if (System .getProperty (LOCKING_SYSTEM_PROPERTY ) != null ) {
6894 return ;
6995 }
@@ -83,8 +109,8 @@ public static void premain(String options, Instrumentation instrumentation) thro
83109 environmentConfigFile );
84110 agentOptions = parseResult .getFirst ();
85111
86- // After parsing everything and configuring logging, we now
87- // can throw the caught exceptions .
112+ // After parsing everything and configuring logging, we now can throw the caught exceptions. They are
113+ // handled locally below and never propagate out of premain (see class-level Javadoc) .
88114 for (Exception exception : parseResult .getSecond ()) {
89115 throw exception ;
90116 }
@@ -120,6 +146,29 @@ public static void premain(String options, Instrumentation instrumentation) thro
120146 agent .registerShutdownHook ();
121147 }
122148
149+ /**
150+ * Logs a startup failure on a best-effort basis. Must not throw: even logging failures are swallowed so that
151+ * {@link #premain(String, Instrumentation)} keeps its no-throw contract.
152+ */
153+ private static void logStartupFailure (Throwable t ) {
154+ try {
155+ LoggingUtils .getLogger (PreMain .class ).error (
156+ "Teamscale Java Profiler failed to start up. The profiled application will continue to run "
157+ + "without coverage collection." , t );
158+ } catch (Throwable loggingFailure ) {
159+ // The logger may not be initialized yet or may itself be broken. Fall back to stderr so the failure is
160+ // at least visible somewhere, then give up silently.
161+ try {
162+ System .err .println (
163+ "[Teamscale Java Profiler] Failed to start up and will not collect coverage: " + t );
164+ loggingFailure .addSuppressed (t );
165+ loggingFailure .printStackTrace ();
166+ } catch (Throwable ignored ) {
167+ // Nothing we can do; the alternative is to crash the profiled application, which we must not do.
168+ }
169+ }
170+ }
171+
123172 private static Pair <AgentOptions , List <Exception >> getAndApplyAgentOptions (String options ,
124173 String environmentConfigId ,
125174 String environmentConfigFile ) throws AgentOptionParseException , IOException , AgentOptionReceiveException {
@@ -208,7 +257,10 @@ private static void initializeLogging(AgentOptions agentOptions, DelayedLogger l
208257 }
209258 }
210259
211- /** Closes the opened logging contexts. */
260+ /**
261+ * Closes the opened logging contexts. Safe to call before logging has been initialized, in which case this is a
262+ * no-op.
263+ */
212264 static void closeLoggingResources () {
213265 if (loggingResources != null ) {
214266 loggingResources .close ();
0 commit comments