Ignore:
Timestamp:
2023-08-24T17:23:11+02:00 (10 months ago)
Author:
taylor.smock
Message:

Fix #23140: RejectedExecutionException when MultiFetchServerObjectReader is cancelled while creating download jobs

This was caused by a race condition.
User starts a download, and cancels it as jobs are submitted to the thread pool.
The next job to be submitted will cause a RejectedExecutionException. In order
to fix this, we ensure that we shutdown the thread pool inside a synchronized
block, and add jobs to the executor inside a synchronized block.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java

    r18129 r18821  
    4848 * Retrieves a set of {@link OsmPrimitive}s from an OSM server using the so called
    4949 * Multi Fetch API.
    50  *
     50 * <p>
    5151 * Usage:
    5252 * <pre>
     
    128128     * Remembers an {@link OsmPrimitive}'s id. The id will
    129129     * later be fetched as part of a Multi Get request.
    130      *
     130     * <p>
    131131     * Ignore the id if it represents a new primitives.
    132132     *
     
    326326        CompletionService<FetchResult> ecs = new ExecutorCompletionService<>(exec);
    327327        List<Future<FetchResult>> jobs = new ArrayList<>();
    328         while (!toFetch.isEmpty() && !isCanceled()) {
    329             jobs.add(ecs.submit(new Fetcher(type, extractIdPackage(toFetch), progressMonitor)));
     328        // There exists a race condition where this is cancelled after isCanceled is called, such that
     329        // the exec ThreadPool has been shut down. This can cause a RejectedExecutionException.
     330        synchronized (this) {
     331            while (!toFetch.isEmpty() && !isCanceled()) {
     332                jobs.add(ecs.submit(new Fetcher(type, extractIdPackage(toFetch), progressMonitor)));
     333            }
    330334        }
    331335        // Run the fetchers
     
    348352                }
    349353            } catch (InterruptedException | ExecutionException e) {
     354                if (e instanceof InterruptedException) {
     355                    Thread.currentThread().interrupt();
     356                }
    350357                Logging.error(e);
    351358                if (e.getCause() instanceof OsmTransferException)
     
    369376     * the latest version of the primitive (if any), even if the primitive is not visible (i.e. if
    370377     * visible==false).
    371      *
     378     * <p>
    372379     * Invoke {@link #getMissingPrimitives()} to get a list of primitives which have not been
    373380     * found on  the server (the server response code was 404)
     
    589596                    }
    590597                    if (pkg.size() == 1) {
    591                         FetchResult res = new FetchResult(new DataSet(), new HashSet<PrimitiveId>());
     598                        FetchResult res = new FetchResult(new DataSet(), new HashSet<>());
    592599                        res.missingPrimitives.add(new SimplePrimitiveId(pkg.iterator().next(), type));
    593600                        return res;
     
    668675         * invokes a sequence of Multi Gets for individual ids in a set of ids and a given {@link OsmPrimitiveType}.
    669676         * The retrieved primitives are merged to {@link #outputDataSet}.
    670          *
     677         * <p>
    671678         * This method is used if one of the ids in pkg doesn't exist (the server replies with return code 404).
    672679         * If the set is fetched with this method it is possible to find out which of the ids doesn't exist.
     
    682689        protected FetchResult singleGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor)
    683690                throws OsmTransferException {
    684             FetchResult result = new FetchResult(new DataSet(), new HashSet<PrimitiveId>());
     691            FetchResult result = new FetchResult(new DataSet(), new HashSet<>());
    685692            String baseUrl = OsmApi.getOsmApi().getBaseUrl();
    686693            for (long id : pkg) {
     
    713720    public void cancel() {
    714721        super.cancel();
    715         if (exec != null)
    716             exec.shutdownNow();
     722        // Synchronized to avoid a RejectedExecutionException in fetchPrimitives
     723        // We don't want to synchronize on the super.cancel() call.
     724        synchronized (this) {
     725            if (exec != null) {
     726                exec.shutdownNow();
     727            }
     728        }
    717729    }
    718730}
Note: See TracChangeset for help on using the changeset viewer.