diff --git a/core/src/main/java/org/web3j/protocol/core/filters/Filter.java b/core/src/main/java/org/web3j/protocol/core/filters/Filter.java index bdca15114..518b239a9 100644 --- a/core/src/main/java/org/web3j/protocol/core/filters/Filter.java +++ b/core/src/main/java/org/web3j/protocol/core/filters/Filter.java @@ -9,6 +9,9 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.web3j.protocol.Web3j; import org.web3j.protocol.core.Request; @@ -23,6 +26,8 @@ */ public abstract class Filter { + private static final Logger log = LoggerFactory.getLogger(Filter.class); + final Web3j web3j; final Callback callback; @@ -43,13 +48,38 @@ public void run(ScheduledExecutorService scheduledExecutorService, long blockTim } filterId = ethFilter.getFilterId(); - - scheduledExecutorService.submit(this::getInitialFilterLogs); - + // this runs in the caller thread as if any exceptions are encountered, we shouldn't + // proceed with creating the scheduled task below + getInitialFilterLogs(); + + /* + We want the filter to be resilient against client issues. On numerous occasions + users have reported socket timeout exceptions when connected over HTTP to Geth and + Parity clients. For examples, refer to + https://github.com/web3j/web3j/issues/144 and + https://github.com/ethereum/go-ethereum/issues/15243. + + Hence we consume errors and log them as errors, allowing our polling for changes to + resume. The downside of this approach is that users will not be notified of + downstream connection issues. But given the intermittent nature of the connection + issues, this seems like a reasonable compromise. + + The alternative approach would be to have another thread that blocks waiting on + schedule.get(), catching any Exceptions thrown, and passing them back up to the + caller. However, the user would then be required to recreate subscriptions manually + which isn't ideal given the aforementioned issues. + */ schedule = scheduledExecutorService.scheduleAtFixedRate( - () -> this.pollFilter(ethFilter), + () -> { + try { + this.pollFilter(ethFilter); + } catch (Throwable e) { + // All exceptions must be caught, otherwise our job terminates without + // any notification + log.error("Error sending request", e); + } + }, 0, blockTime, TimeUnit.MILLISECONDS); - } catch (IOException e) { throwException(e); } @@ -66,6 +96,7 @@ private void getInitialFilterLogs() { ethLog.setResult(Collections.emptyList()); } process(ethLog.getLogs()); + } catch (IOException e) { throwException(e); } @@ -80,8 +111,9 @@ private void pollFilter(EthFilter ethFilter) { } if (ethLog.hasError()) { throwException(ethFilter.getError()); + } else { + process(ethLog.getLogs()); } - process(ethLog.getLogs()); } abstract EthFilter sendRequest() throws IOException; diff --git a/core/src/main/java/org/web3j/protocol/http/HttpService.java b/core/src/main/java/org/web3j/protocol/http/HttpService.java index f446eb3eb..b8a4df2aa 100644 --- a/core/src/main/java/org/web3j/protocol/http/HttpService.java +++ b/core/src/main/java/org/web3j/protocol/http/HttpService.java @@ -18,8 +18,6 @@ import org.slf4j.LoggerFactory; import org.web3j.protocol.Service; -import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.Response; import org.web3j.protocol.exceptions.ClientConnectionException; /** diff --git a/docs/source/images/transaction_process.png b/docs/source/images/transaction_process.png index e64b37da8..69730fe2c 100644 Binary files a/docs/source/images/transaction_process.png and b/docs/source/images/transaction_process.png differ diff --git a/docs/source/images/web3j_transaction.png b/docs/source/images/web3j_transaction.png index 704ea30b7..f6b918505 100644 Binary files a/docs/source/images/web3j_transaction.png and b/docs/source/images/web3j_transaction.png differ