diff --git a/examples/webserver/fault-tolerance/pom.xml b/examples/webserver/fault-tolerance/pom.xml
new file mode 100644
index 000000000..0ed98ab63
--- /dev/null
+++ b/examples/webserver/fault-tolerance/pom.xml
@@ -0,0 +1,85 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 2.0.1-SNAPSHOT
+ ../../../applications/se/pom.xml
+
+
+ io.helidon.examples.webserver
+ helidon-examples-webserver-fault-tolerance
+ Helidon WebServer Examples FT
+
+
+ Application demonstrates Fault tolerance used in webserver.
+
+
+
+ io.helidon.webserver.examples.faulttolerance.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.fault-tolerance
+ helidon-fault-tolerance
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.webserver
+ helidon-webserver-test-support
+ test
+
+
+ io.helidon.webclient
+ helidon-webclient
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/FtService.java b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/FtService.java
new file mode 100644
index 000000000..30442fc5c
--- /dev/null
+++ b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/FtService.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2020 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.webserver.examples.faulttolerance;
+
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.helidon.common.reactive.Single;
+import io.helidon.faulttolerance.Async;
+import io.helidon.faulttolerance.Bulkhead;
+import io.helidon.faulttolerance.CircuitBreaker;
+import io.helidon.faulttolerance.Fallback;
+import io.helidon.faulttolerance.Retry;
+import io.helidon.faulttolerance.Timeout;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+
+/**
+ * Simple service to demonstrate fault tolerance.
+ */
+public class FtService implements Service {
+
+ private final Async async;
+ private final Bulkhead bulkhead;
+ private final CircuitBreaker breaker;
+ private final Fallback fallback;
+ private final Retry retry;
+ private final Timeout timeout;
+
+ FtService() {
+ this.async = Async.create();
+ this.bulkhead = Bulkhead.builder()
+ .queueLength(1)
+ .limit(1)
+ .name("helidon-example-bulkhead")
+ .build();
+ this.breaker = CircuitBreaker.builder()
+ .volume(10)
+ .errorRatio(20)
+ .successThreshold(1)
+ .delay(Duration.ofSeconds(5))
+ .build();
+ this.fallback = Fallback.create(this::fallbackToMethod);
+ this.retry = Retry.builder()
+ .retryPolicy(Retry.DelayingRetryPolicy.noDelay(3))
+ .build();
+ this.timeout = Timeout.create(Duration.ofMillis(100));
+ }
+
+ @Override
+ public void update(Routing.Rules rules) {
+ rules.get("/async", this::async)
+ .get("/bulkhead/{millis}", this::bulkhead)
+ .get("/circuitBreaker/{success}", this::circuitBreaker)
+ .get("/fallback/{success}", this::fallback)
+ .get("/retry/{count}", this::retry)
+ .get("/timeout/{millis}", this::timeout);
+ }
+
+ private void timeout(ServerRequest request, ServerResponse response) {
+ long sleep = Long.parseLong(request.path().param("millis"));
+
+ timeout.invoke(() -> sleep(sleep))
+ .thenAccept(response::send)
+ .exceptionally(response::send);
+ }
+
+ private void retry(ServerRequest request, ServerResponse response) {
+ int count = Integer.parseInt(request.path().param("count"));
+
+ AtomicInteger call = new AtomicInteger(1);
+ AtomicInteger failures = new AtomicInteger();
+
+ retry.invoke(() -> {
+ int current = call.getAndIncrement();
+ if (current < count) {
+ failures.incrementAndGet();
+ return reactiveFailure();
+ }
+ return Single.just("calls/failures: " + current + "/" + failures.get());
+ }).thenAccept(response::send)
+ .exceptionally(response::send);
+ }
+
+ private void fallback(ServerRequest request, ServerResponse response) {
+ boolean success = "true".equalsIgnoreCase(request.path().param("success"));
+
+ if (success) {
+ fallback.invoke(this::reactiveData).thenAccept(response::send);
+ } else {
+ fallback.invoke(this::reactiveFailure).thenAccept(response::send);
+ }
+ }
+
+ private void circuitBreaker(ServerRequest request, ServerResponse response) {
+ boolean success = "true".equalsIgnoreCase(request.path().param("success"));
+
+ if (success) {
+ breaker.invoke(this::reactiveData)
+ .thenAccept(response::send)
+ .exceptionally(response::send);
+ } else {
+ breaker.invoke(this::reactiveFailure)
+ .thenAccept(response::send)
+ .exceptionally(response::send);
+ }
+
+ }
+
+ private void bulkhead(ServerRequest request, ServerResponse response) {
+ long sleep = Long.parseLong(request.path().param("millis"));
+
+ bulkhead.invoke(() -> sleep(sleep))
+ .thenAccept(response::send)
+ .exceptionally(response::send);
+ }
+
+ private void async(ServerRequest request, ServerResponse response) {
+ async.invoke(this::blockingData).thenApply(response::send);
+ }
+
+ private Single reactiveFailure() {
+ return Single.error(new RuntimeException("reactive failure"));
+ }
+
+ private Single sleep(long sleepMillis) {
+ return async.invoke(() -> {
+ try {
+ Thread.sleep(sleepMillis);
+ } catch (InterruptedException e) {
+ }
+ return "Slept for " + sleepMillis + " ms";
+ });
+ }
+
+ private Single reactiveData() {
+ return async.invoke(this::blockingData);
+ }
+
+ private String blockingData() {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ }
+ return "blocked for 100 millis";
+ }
+
+ private Single fallbackToMethod(Throwable e) {
+ return Single.just("Failed back because of " + e.getMessage());
+ }
+
+}
diff --git a/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/Main.java b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/Main.java
new file mode 100644
index 000000000..fb023f7e1
--- /dev/null
+++ b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/Main.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.webserver.examples.faulttolerance;
+
+import java.util.concurrent.TimeoutException;
+
+import io.helidon.common.LogConfig;
+import io.helidon.common.http.Http;
+import io.helidon.common.reactive.Single;
+import io.helidon.faulttolerance.BulkheadException;
+import io.helidon.faulttolerance.CircuitBreakerOpenException;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.WebServer;
+
+/**
+ * Main class of Fault tolerance example.
+ */
+public final class Main {
+ // utility class
+ private Main() {
+ }
+
+ /**
+ * Start the example.
+ *
+ * @param args start arguments are ignored
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ startServer(8079).thenRun(() -> {});
+ }
+
+ static Single startServer(int port) {
+ return WebServer.builder()
+ .routing(routing())
+ .port(port)
+ .build()
+ .start()
+ .peek(server -> {
+ String url = "http://localhost:" + server.port();
+ System.out.println("Server started on " + url);
+ });
+ }
+
+ private static Routing routing() {
+ return Routing.builder()
+ .register("/ft", new FtService())
+ .error(BulkheadException.class,
+ (req, res, ex) -> res.status(Http.Status.SERVICE_UNAVAILABLE_503).send("bulkhead"))
+ .error(CircuitBreakerOpenException.class,
+ (req, res, ex) -> res.status(Http.Status.SERVICE_UNAVAILABLE_503).send("circuit breaker"))
+ .error(TimeoutException.class,
+ (req, res, ex) -> res.status(Http.Status.REQUEST_TIMEOUT_408).send("timeout"))
+ .error(Throwable.class,
+ (req, res, ex) -> res.status(Http.Status.INTERNAL_SERVER_ERROR_500)
+ .send(ex.getClass().getName() + ": " + ex.getMessage()))
+ .build();
+ }
+}
diff --git a/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/package-info.java b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/package-info.java
new file mode 100644
index 000000000..f19f38365
--- /dev/null
+++ b/examples/webserver/fault-tolerance/src/main/java/io/helidon/webserver/examples/faulttolerance/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Example of Fault Tolerance usage in webserver.
+ */
+package io.helidon.webserver.examples.faulttolerance;
diff --git a/examples/webserver/fault-tolerance/src/main/resources/logging.properties b/examples/webserver/fault-tolerance/src/main/resources/logging.properties
new file mode 100644
index 000000000..f34ee0f6b
--- /dev/null
+++ b/examples/webserver/fault-tolerance/src/main/resources/logging.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+handlers=io.helidon.common.HelidonConsoleHandler
+java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n
+.level=INFO
+io.helidon.microprofile.server.level=INFO
+
+#io.helidon.faulttolerance.level=FINEST
diff --git a/examples/webserver/fault-tolerance/src/test/java/io/helidon/webserver/examples/faulttolerance/MainTest.java b/examples/webserver/fault-tolerance/src/test/java/io/helidon/webserver/examples/faulttolerance/MainTest.java
new file mode 100644
index 000000000..250b6672f
--- /dev/null
+++ b/examples/webserver/fault-tolerance/src/test/java/io/helidon/webserver/examples/faulttolerance/MainTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.helidon.webserver.examples.faulttolerance;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import io.helidon.common.http.Http;
+import io.helidon.webclient.WebClient;
+import io.helidon.webclient.WebClientResponse;
+import io.helidon.webserver.WebServer;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class MainTest {
+ private static WebServer server;
+ private static WebClient client;
+
+ @BeforeAll
+ static void initClass() throws ExecutionException, InterruptedException {
+ server = Main.startServer(0)
+ .await(10, TimeUnit.SECONDS);
+
+ client = WebClient.builder()
+ .baseUri("http://localhost:" + server.port() + "/ft")
+ .build();
+ }
+
+ @AfterAll
+ static void destroyClass() {
+ server.shutdown()
+ .await(5, TimeUnit.SECONDS);
+ }
+
+ @Test
+ void testAsync() {
+ String response = client.get()
+ .path("/async")
+ .request(String.class)
+ .await(5, TimeUnit.SECONDS);
+
+ assertThat(response, is("blocked for 100 millis"));
+ }
+
+ @Test
+ void testBulkhead() throws InterruptedException {
+ // bulkhead is configured for limit of 1 and queue of 1, so third
+ // request should fail
+ client.get()
+ .path("/bulkhead/100000")
+ .request()
+ .thenRun(() -> {});
+
+ client.get()
+ .path("/bulkhead/100000")
+ .request()
+ .thenRun(() -> {});
+
+ // I want to make sure the above is connected
+ Thread.sleep(100);
+
+ WebClientResponse third = client.get()
+ .path("/bulkhead/10000")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+
+ // registered an error handler in Main
+ assertThat(third.status(), is(Http.Status.SERVICE_UNAVAILABLE_503));
+ assertThat(third.content().as(String.class).await(1, TimeUnit.SECONDS), is("bulkhead"));
+ }
+
+ @Test
+ void testCircuitBreaker() {
+ String response = client.get()
+ .path("/circuitBreaker/true")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("blocked for 100 millis"));
+
+ // error ratio is 20% within 10 request
+ client.get()
+ .path("/circuitBreaker/false")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+
+ // should work after first
+ response = client.get()
+ .path("/circuitBreaker/true")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("blocked for 100 millis"));
+
+ // should open after second
+ client.get()
+ .path("/circuitBreaker/false")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+
+ WebClientResponse clientResponse = client.get()
+ .path("/circuitBreaker/true")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+ response = clientResponse.content().as(String.class).await(1, TimeUnit.SECONDS);
+
+ // registered an error handler in Main
+ assertThat(clientResponse.status(), is(Http.Status.SERVICE_UNAVAILABLE_503));
+ assertThat(response, is("circuit breaker"));
+ }
+
+ @Test
+ void testFallback() {
+ String response = client.get()
+ .path("/fallback/true")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("blocked for 100 millis"));
+
+ response = client.get()
+ .path("/fallback/false")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("Failed back because of reactive failure"));
+ }
+
+ @Test
+ void testRetry() {
+ String response = client.get()
+ .path("/retry/1")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("calls/failures: 1/0"));
+
+ response = client.get()
+ .path("/retry/2")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("calls/failures: 2/1"));
+
+ response = client.get()
+ .path("/retry/3")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("calls/failures: 3/2"));
+
+ WebClientResponse clientResponse = client.get()
+ .path("/retry/4")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+
+ response = clientResponse.content().as(String.class).await(1, TimeUnit.SECONDS);
+ // no error handler specified
+ assertThat(clientResponse.status(), is(Http.Status.INTERNAL_SERVER_ERROR_500));
+ assertThat(response, is("java.lang.RuntimeException: reactive failure"));
+ }
+
+ @Test
+ void testTimeout() {
+ String response = client.get()
+ .path("/timeout/50")
+ .request(String.class)
+ .await(1, TimeUnit.SECONDS);
+
+ assertThat(response, is("Slept for 50 ms"));
+
+ WebClientResponse clientResponse = client.get()
+ .path("/timeout/105")
+ .request()
+ .await(1, TimeUnit.SECONDS);
+
+ response = clientResponse.content().as(String.class).await(1, TimeUnit.SECONDS);
+ // error handler specified in Main
+ assertThat(clientResponse.status(), is(Http.Status.REQUEST_TIMEOUT_408));
+ assertThat(response, is("timeout"));
+ }
+}
\ No newline at end of file
diff --git a/examples/webserver/pom.xml b/examples/webserver/pom.xml
index 4abee72a9..7f08b582b 100644
--- a/examples/webserver/pom.xml
+++ b/examples/webserver/pom.xml
@@ -42,5 +42,6 @@
websocket
tls
mutual-tls
+ fault-tolerance
diff --git a/examples/webserver/tls/pom.xml b/examples/webserver/tls/pom.xml
index b12c9c1d5..5feb4d944 100644
--- a/examples/webserver/tls/pom.xml
+++ b/examples/webserver/tls/pom.xml
@@ -73,11 +73,6 @@
helidon-webserver-test-support
test
-
- io.helidon.webclient
- helidon-webclient
- test
-