Skip to content

Commit 1cc04fe

Browse files
committed
better implementation of shutdown on startup error, fix jooby-project#451
1 parent 76223e2 commit 1cc04fe

File tree

6 files changed

+114
-237
lines changed

6 files changed

+114
-237
lines changed

jooby-maven-plugin/src/main/java/org/jooby/JoobyRunner.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ public void run(final String mainClass, final Consumer<Jooby> callback)
6060
Thread.currentThread().setContextClassLoader(local);
6161
Jooby app = (Jooby) local.loadClass(mainClass).newInstance();
6262
callback.accept(app);
63-
app.start(routes);
63+
if (routes != null) {
64+
routes.accept(Jooby.exportRoutes(app));
65+
}
6466
} finally {
6567
Thread.currentThread().setContextClassLoader(global);
6668
}

jooby-spec/src/main/java/org/jooby/spec/RouteProcessor.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,6 @@
7777
*/
7878
public class RouteProcessor {
7979

80-
/**
81-
* Force cancellation of startup. Internal use.
82-
*/
83-
@SuppressWarnings("serial")
84-
private static class StopEx extends RuntimeException {
85-
}
86-
8780
/** The logging system. */
8881
private final Logger log = LoggerFactory.getLogger(getClass());
8982

@@ -184,19 +177,7 @@ public List<RouteSpec> compile(final Class<? extends Jooby> appClass,
184177
* @return List of route specs.
185178
*/
186179
private List<RouteSpec> processInternal(final Jooby app, final Path srcdir, final Path outdir) {
187-
List<Route.Definition> routes = new ArrayList<>();
188-
try {
189-
app.start(r -> {
190-
routes.addAll(r);
191-
throw new StopEx();
192-
});
193-
} catch (StopEx cause) {
194-
log.trace("routes were collected successfully");
195-
} catch (Throwable cause) {
196-
log.error("Unable to get routes from {}", app, cause);
197-
return Collections.emptyList();
198-
}
199-
180+
List<Route.Definition> routes = Jooby.exportRoutes(app);
200181
return processInternal(app.getClass(), routes, srcdir, outdir);
201182
}
202183

jooby/src/main/java/org/jooby/Jooby.java

Lines changed: 84 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import java.util.ArrayList;
6767
import java.util.Arrays;
6868
import java.util.Collection;
69+
import java.util.Collections;
6970
import java.util.HashMap;
7071
import java.util.HashSet;
7172
import java.util.LinkedHashSet;
@@ -131,7 +132,6 @@
131132
import com.google.common.collect.ImmutableList;
132133
import com.google.common.collect.ImmutableMap;
133134
import com.google.common.collect.ImmutableSet;
134-
import com.google.common.collect.Lists;
135135
import com.google.inject.Binder;
136136
import com.google.inject.Guice;
137137
import com.google.inject.Injector;
@@ -3497,54 +3497,31 @@ public static void run(final Class<? extends Jooby> app, final String... args) {
34973497
}
34983498

34993499
/**
3500-
* <h1>Bootstrap</h1>
3501-
* <p>
3502-
* The bootstrap process is defined as follows:
3503-
* </p>
3504-
* <h2>1. Configuration files (first-listed are higher priority)</h2>
3505-
* <ol>
3506-
* <li>System properties</li>
3507-
* <li>Application properties: {@code application.conf} or custom, see {@link #use(Config)}</li>
3508-
* <li>{@link Jooby.Module Modules} properties</li>
3509-
* </ol>
3500+
* Export routes from an application. Useful for route analysis, testing, debugging, etc...
35103501
*
3511-
* <h2>2. Dependency Injection and {@link Jooby.Module modules}</h2>
3512-
* <ol>
3513-
* <li>An {@link Injector Guice Injector} is created.</li>
3514-
* <li>It calls to {@link Jooby.Module#configure(Env, Config, Binder)} for each module.</li>
3515-
* <li>At this point Guice is ready and all the services has been binded.</li>
3516-
* <li>A web server is started</li>
3517-
* </ol>
3502+
* @param app Application to extract/collect routes.
3503+
* @return Application routes.
35183504
*/
3519-
public void start() {
3520-
start(new String[0]);
3521-
}
3505+
public static List<Definition> exportRoutes(final Jooby app) {
3506+
@SuppressWarnings("serial")
3507+
class Success extends RuntimeException {
3508+
List<Definition> routes;
35223509

3523-
/**
3524-
* <h1>Bootstrap</h1>
3525-
* <p>
3526-
* The bootstrap process is defined as follows:
3527-
* </p>
3528-
* <h2>1. Configuration files (first-listed are higher priority)</h2>
3529-
* <ol>
3530-
* <li>System properties</li>
3531-
* <li>Application properties: {@code application.conf} or custom, see {@link #use(Config)}</li>
3532-
* <li>{@link Jooby.Module Modules} properties</li>
3533-
* </ol>
3534-
*
3535-
* <h2>2. Dependency Injection and {@link Jooby.Module modules}</h2>
3536-
* <ol>
3537-
* <li>An {@link Injector Guice Injector} is created.</li>
3538-
* <li>It calls to {@link Jooby.Module#configure(Env, Config, Binder)} for each module.</li>
3539-
* <li>At this point Guice is ready and all the services has been binded.</li>
3540-
* <li>A web server is started</li>
3541-
* </ol>
3542-
*
3543-
* @param routes Routes callback. Invoked once all app routes has been collected.
3544-
* @throws Throwable If something fails to start.
3545-
*/
3546-
public void start(final Consumer<List<Route.Definition>> routes) throws Throwable {
3547-
start(new String[0], routes);
3510+
Success(final List<Route.Definition> routes) {
3511+
this.routes = routes;
3512+
}
3513+
}
3514+
List<Definition> routes = Collections.emptyList();
3515+
try {
3516+
app.start(new String[0], r -> {
3517+
throw new Success(r);
3518+
});
3519+
} catch (Success success) {
3520+
routes = success.routes;
3521+
} catch (Throwable x) {
3522+
logger(app).error("Unable to get routes from {}", app, x);
3523+
}
3524+
return routes;
35483525
}
35493526

35503527
/**
@@ -3566,12 +3543,9 @@ public void start(final Consumer<List<Route.Definition>> routes) throws Throwabl
35663543
* <li>At this point Guice is ready and all the services has been binded.</li>
35673544
* <li>A web server is started</li>
35683545
* </ol>
3569-
*
3570-
* @param args Application arguments. Using the <code>name=value</code> format, except for
3571-
* application.env where can be just: <code>myenv</code>.
35723546
*/
3573-
public void start(final String[] args) {
3574-
start(args, null);
3547+
public void start() {
3548+
start(new String[0]);
35753549
}
35763550

35773551
/**
@@ -3596,55 +3570,59 @@ public void start(final String[] args) {
35963570
*
35973571
* @param args Application arguments. Using the <code>name=value</code> format, except for
35983572
* application.env where can be just: <code>myenv</code>.
3599-
* @param routes Routes callback. Invoked once all app routes has been collected.
36003573
*/
3601-
public void start(final String[] args, final Consumer<List<Route.Definition>> routes) {
3574+
public void start(final String... args) {
36023575
try {
3603-
long start = System.currentTimeMillis();
3576+
start(args, null);
3577+
} catch (Throwable x) {
3578+
stop(Optional.of(x));
3579+
}
3580+
}
36043581

3605-
started.set(true);
3582+
private void start(final String[] args, final Consumer<List<Route.Definition>> routes)
3583+
throws Throwable {
3584+
long start = System.currentTimeMillis();
36063585

3607-
// shutdown hook
3608-
Runtime.getRuntime().addShutdownHook(new Thread(() -> stop()));
3586+
started.set(true);
36093587

3610-
this.injector = bootstrap(args(args), routes);
3588+
this.injector = bootstrap(args(args), routes);
36113589

3612-
Config conf = injector.getInstance(Config.class);
3590+
// shutdown hook
3591+
Runtime.getRuntime().addShutdownHook(new Thread(() -> stop()));
36133592

3614-
Logger log = LoggerFactory.getLogger(getClass());
3615-
log.debug("config tree:\n{}", configTree(conf.origin().description()));
3593+
Config conf = injector.getInstance(Config.class);
36163594

3617-
// start services
3618-
for (CheckedConsumer<Registry> onStart : this.onStart) {
3619-
onStart.accept(this);
3620-
}
3595+
Logger log = LoggerFactory.getLogger(getClass());
3596+
log.debug("config tree:\n{}", configTree(conf.origin().description()));
36213597

3622-
// route mapper
3623-
Set<Route.Definition> routeDefs = injector.getInstance(Route.KEY);
3624-
Set<WebSocket.Definition> sockets = injector.getInstance(WebSocket.KEY);
3625-
if (mapper != null) {
3626-
routeDefs.forEach(it -> it.map(mapper));
3627-
}
3598+
// start services
3599+
for (CheckedConsumer<Registry> onStart : this.onStart) {
3600+
onStart.accept(this);
3601+
}
36283602

3629-
// Start server
3630-
Server server = injector.getInstance(Server.class);
3631-
String serverName = server.getClass().getSimpleName().replace("Server", "").toLowerCase();
3603+
// route mapper
3604+
Set<Route.Definition> routeDefs = injector.getInstance(Route.KEY);
3605+
Set<WebSocket.Definition> sockets = injector.getInstance(WebSocket.KEY);
3606+
if (mapper != null) {
3607+
routeDefs.forEach(it -> it.map(mapper));
3608+
}
36323609

3633-
server.start();
3634-
long end = System.currentTimeMillis();
3610+
// Start server
3611+
Server server = injector.getInstance(Server.class);
3612+
String serverName = server.getClass().getSimpleName().replace("Server", "").toLowerCase();
36353613

3636-
log.info("[{}@{}]: Server started in {}ms\n\n{}\n",
3637-
conf.getString("application.env"),
3638-
serverName,
3639-
end - start,
3640-
new AppPrinter(routeDefs, sockets, conf));
3614+
server.start();
3615+
long end = System.currentTimeMillis();
36413616

3642-
boolean join = conf.hasPath("server.join") ? conf.getBoolean("server.join") : true;
3643-
if (join) {
3644-
server.join();
3645-
}
3646-
} catch (Throwable x) {
3647-
stop(Optional.of(x));
3617+
log.info("[{}@{}]: Server started in {}ms\n\n{}\n",
3618+
conf.getString("application.env"),
3619+
serverName,
3620+
end - start,
3621+
new AppPrinter(routeDefs, sockets, conf));
3622+
3623+
boolean join = conf.hasPath("server.join") ? conf.getBoolean("server.join") : true;
3624+
if (join) {
3625+
server.join();
36483626
}
36493627
}
36503628

@@ -4058,11 +4036,11 @@ private Injector bootstrap(final Config args,
40584036
List<Object> bag = normalize(realbag, env, rm, prefix);
40594037

40604038
// collect routes and fire route callback
4061-
List<Route.Definition> routes = bag.stream()
4062-
.filter(it -> it instanceof Route.Definition)
4063-
.map(it -> (Route.Definition) it)
4064-
.collect(Collectors.<Route.Definition> toList());
40654039
if (rcallback != null) {
4040+
List<Route.Definition> routes = bag.stream()
4041+
.filter(it -> it instanceof Route.Definition)
4042+
.map(it -> (Route.Definition) it)
4043+
.collect(Collectors.<Route.Definition> toList());
40664044
rcallback.accept(routes);
40674045
}
40684046

@@ -4354,22 +4332,20 @@ public void stop() {
43544332

43554333
private void stop(final Optional<Throwable> x) {
43564334
if (started.compareAndSet(true, false)) {
4357-
Logger log = LoggerFactory.getLogger(getClass());
4358-
fireStop(injector, this, log, onStop);
4335+
Logger log = logger(this);
43594336

4360-
List<Throwable> cause = Lists.newArrayList();
4361-
x.ifPresent(cause::add);
4362-
try {
4363-
injector.getInstance(Server.class).stop();
4364-
} catch (Throwable ex) {
4365-
cause.add(ex);
4366-
}
4367-
if (cause.size() > 0) {
4368-
log.error("Shutdown after error", cause.get(0));
4369-
} else {
4370-
log.info("Shutdown successfully");
4337+
x.ifPresent(c -> log.error("An error occurred while starting the application:", c));
4338+
fireStop(injector, this, log, onStop);
4339+
if (injector != null) {
4340+
try {
4341+
injector.getInstance(Server.class).stop();
4342+
} catch (Throwable ex) {
4343+
log.debug("server.stop() resulted in exception", ex);
4344+
}
43714345
}
43724346
injector = null;
4347+
4348+
log.info("Stopped");
43734349
}
43744350
}
43754351

@@ -4676,4 +4652,8 @@ static String logback(final Config conf) {
46764652
return logback;
46774653
}
46784654

4655+
private static Logger logger(final Jooby app) {
4656+
return LoggerFactory.getLogger(app.getClass());
4657+
}
4658+
46794659
}

jooby/src/test/java/org/jooby/JoobyRunTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jooby;
22

3-
import static org.easymock.EasyMock.aryEq;
43
import static org.easymock.EasyMock.expect;
54

65
import java.util.Arrays;
@@ -27,13 +26,13 @@ public ArgEx(final String[] args) {
2726

2827
public static class NoopApp extends Jooby {
2928
@Override
30-
public void start(final String[] args) {
29+
public void start(final String... args) {
3130
}
3231
}
3332

3433
public static class NoopAppEx extends Jooby {
3534
@Override
36-
public void start(final String[] args) {
35+
public void start(final String... args) {
3736
throw new ArgEx(args);
3837
}
3938
}
@@ -49,7 +48,7 @@ public void runSupplier() throws Exception {
4948
})
5049
.expect(unit -> {
5150
Jooby jooby = unit.get(Jooby.class);
52-
jooby.start(aryEq(args));
51+
jooby.start(args);
5352
})
5453
.run(unit -> {
5554
Jooby.run(unit.get(Supplier.class), args);
@@ -67,7 +66,7 @@ public void runSupplierArg() throws Exception {
6766
})
6867
.expect(unit -> {
6968
Jooby jooby = unit.get(Jooby.class);
70-
jooby.start(aryEq(args));
69+
jooby.start(args);
7170
})
7271
.run(unit -> {
7372
Jooby.run(unit.get(Supplier.class), args);

0 commit comments

Comments
 (0)