- Timestamp:
- 2023-08-24T17:23:11+02:00 (19 months ago)
- Location:
- trunk
- Files:
-
- 2 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
r18129 r18821 48 48 * Retrieves a set of {@link OsmPrimitive}s from an OSM server using the so called 49 49 * Multi Fetch API. 50 * 50 * <p> 51 51 * Usage: 52 52 * <pre> … … 128 128 * Remembers an {@link OsmPrimitive}'s id. The id will 129 129 * later be fetched as part of a Multi Get request. 130 * 130 * <p> 131 131 * Ignore the id if it represents a new primitives. 132 132 * … … 326 326 CompletionService<FetchResult> ecs = new ExecutorCompletionService<>(exec); 327 327 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 } 330 334 } 331 335 // Run the fetchers … … 348 352 } 349 353 } catch (InterruptedException | ExecutionException e) { 354 if (e instanceof InterruptedException) { 355 Thread.currentThread().interrupt(); 356 } 350 357 Logging.error(e); 351 358 if (e.getCause() instanceof OsmTransferException) … … 369 376 * the latest version of the primitive (if any), even if the primitive is not visible (i.e. if 370 377 * visible==false). 371 * 378 * <p> 372 379 * Invoke {@link #getMissingPrimitives()} to get a list of primitives which have not been 373 380 * found on the server (the server response code was 404) … … 589 596 } 590 597 if (pkg.size() == 1) { 591 FetchResult res = new FetchResult(new DataSet(), new HashSet< PrimitiveId>());598 FetchResult res = new FetchResult(new DataSet(), new HashSet<>()); 592 599 res.missingPrimitives.add(new SimplePrimitiveId(pkg.iterator().next(), type)); 593 600 return res; … … 668 675 * invokes a sequence of Multi Gets for individual ids in a set of ids and a given {@link OsmPrimitiveType}. 669 676 * The retrieved primitives are merged to {@link #outputDataSet}. 670 * 677 * <p> 671 678 * This method is used if one of the ids in pkg doesn't exist (the server replies with return code 404). 672 679 * If the set is fetched with this method it is possible to find out which of the ids doesn't exist. … … 682 689 protected FetchResult singleGetIdPackage(OsmPrimitiveType type, Set<Long> pkg, ProgressMonitor progressMonitor) 683 690 throws OsmTransferException { 684 FetchResult result = new FetchResult(new DataSet(), new HashSet< PrimitiveId>());691 FetchResult result = new FetchResult(new DataSet(), new HashSet<>()); 685 692 String baseUrl = OsmApi.getOsmApi().getBaseUrl(); 686 693 for (long id : pkg) { … … 713 720 public void cancel() { 714 721 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 } 717 729 } 718 730 } -
trunk/test/functional/org/openstreetmap/josm/io/MultiFetchServerObjectReaderTest.java
r18691 r18821 5 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 6 import static org.junit.jupiter.api.Assertions.assertNotNull; 7 import static org.junit.jupiter.api.Assertions.assertNull; 7 8 import static org.junit.jupiter.api.Assertions.assertTrue; 8 import static org.junit.jupiter.api.Assumptions.assumeTrue;9 9 10 10 import java.io.File; … … 23 23 import java.util.Random; 24 24 import java.util.TreeSet; 25 import java.util.concurrent.RejectedExecutionException; 25 26 import java.util.concurrent.TimeUnit; 27 import java.util.concurrent.atomic.AtomicBoolean; 28 import java.util.concurrent.atomic.AtomicInteger; 29 import java.util.concurrent.atomic.AtomicReference; 26 30 import java.util.logging.Logger; 27 31 32 import org.awaitility.Awaitility; 33 import org.awaitility.Durations; 34 import org.awaitility.core.ConditionTimeoutException; 35 import org.hamcrest.Matchers; 28 36 import org.junit.jupiter.api.BeforeAll; 29 37 import org.junit.jupiter.api.BeforeEach; 30 38 import org.junit.jupiter.api.Test; 31 39 import org.junit.jupiter.api.Timeout; 32 import org.junit.jupiter.api.extension.RegisterExtension;33 import org.openstreetmap.josm.JOSMFixture;34 import org.openstreetmap.josm.TestUtils;35 40 import org.openstreetmap.josm.data.coor.LatLon; 36 41 import org.openstreetmap.josm.data.osm.Changeset; … … 43 48 import org.openstreetmap.josm.data.osm.Way; 44 49 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 45 import org.openstreetmap.josm.spi.preferences.Config; 46 import org.openstreetmap.josm.testutils.JOSMTestRules; 50 import org.openstreetmap.josm.gui.util.GuiHelper; 51 import org.openstreetmap.josm.testutils.annotations.TestUser; 52 import org.openstreetmap.josm.tools.Logging; 47 53 48 54 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 53 59 @SuppressFBWarnings(value = "CRLF_INJECTION_LOGS") 54 60 @Timeout(value = 1, unit = TimeUnit.MINUTES) 61 @org.openstreetmap.josm.testutils.annotations.OsmApi(org.openstreetmap.josm.testutils.annotations.OsmApi.APIType.DEV) 62 @TestUser 55 63 class MultiFetchServerObjectReaderTest { 56 64 private static final Logger logger = Logger.getLogger(MultiFetchServerObjectReader.class.getName()); 57 58 /**59 * Setup test.60 */61 @RegisterExtension62 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")63 public JOSMTestRules test = new JOSMTestRules().preferences();64 65 65 66 /** … … 159 160 @BeforeAll 160 161 public static void init() throws Exception { 161 if (!TestUtils.areCredentialsProvided()) {162 logger.severe("OSM DEV API credentials not provided. Please define them with -Dosm.username and -Dosm.password");163 return;164 }165 162 logger.info("initializing ..."); 166 JOSMFixture.createFunctionalTestFixture().init();167 168 Config.getPref().put("osm-server.auth-method", "basic");169 170 // don't use atomic upload, the test API server can't cope with large diff uploads171 Config.getPref().putBoolean("osm-server.atomic-upload", false);172 163 173 164 File dataSetCacheOutputFile = new File(System.getProperty("java.io.tmpdir"), … … 213 204 @BeforeEach 214 205 public void setUp() throws IOException, IllegalDataException, FileNotFoundException { 215 if (!TestUtils.areCredentialsProvided()) {216 return;217 }218 206 File f = new File(System.getProperty("java.io.tmpdir"), MultiFetchServerObjectReaderTest.class.getName() + ".dataset"); 219 207 logger.info(MessageFormat.format("reading cached dataset ''{0}''", f.toString())); … … 230 218 @Test 231 219 void testMultiGet10Nodes() throws OsmTransferException { 232 assumeTrue(TestUtils.areCredentialsProvided());233 220 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 234 221 ArrayList<Node> nodes = new ArrayList<>(ds.getNodes()); … … 252 239 @Test 253 240 void testMultiGet10Ways() throws OsmTransferException { 254 assumeTrue(TestUtils.areCredentialsProvided());255 241 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 256 242 ArrayList<Way> ways = new ArrayList<>(ds.getWays()); … … 275 261 @Test 276 262 void testMultiGet10Relations() throws OsmTransferException { 277 assumeTrue(TestUtils.areCredentialsProvided());278 263 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 279 264 ArrayList<Relation> relations = new ArrayList<>(ds.getRelations()); … … 298 283 @Test 299 284 void testMultiGet800Nodes() throws OsmTransferException { 300 assumeTrue(TestUtils.areCredentialsProvided());301 285 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 302 286 ArrayList<Node> nodes = new ArrayList<>(ds.getNodes()); … … 320 304 @Test 321 305 void testMultiGetWithNonExistingNode() throws OsmTransferException { 322 assumeTrue(TestUtils.areCredentialsProvided());323 306 MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); 324 307 ArrayList<Node> nodes = new ArrayList<>(ds.getNodes()); … … 349 332 assertEquals("ways?ways=123,126,130", requestString); 350 333 } 334 335 /** 336 * This is a non-regression test for #23140: Cancelling `MultiFetchServerObjectReader` while it is adding jobs 337 * to the executor causes a {@link RejectedExecutionException}. 338 * This was caused by a race condition between {@link MultiFetchServerObjectReader#cancel()} and queuing download 339 * jobs. 340 */ 341 @Test 342 void testCancelDuringJobAdd() { 343 final AtomicBoolean parsedData = new AtomicBoolean(); 344 final AtomicBoolean continueAddition = new AtomicBoolean(); 345 final AtomicInteger callCounter = new AtomicInteger(); 346 final AtomicReference<Throwable> thrownFailure = new AtomicReference<>(); 347 // We have 5 + 10 maximum (5 previous calls, 10 calls when iterating through the nodes). 348 final int expectedCancelCalls = 5; 349 final MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader() { 350 @Override 351 public boolean isCanceled() { 352 final boolean result = super.isCanceled(); 353 // There are some calls prior to the location where we are interested 354 if (callCounter.incrementAndGet() >= expectedCancelCalls) { 355 // This will throw a ConditionTimeoutException. 356 // By blocking here until cancel() is called, we block cancel (since we are interested in a loop). 357 Awaitility.await().timeout(Durations.FIVE_HUNDRED_MILLISECONDS).untilTrue(continueAddition); 358 } 359 return result; 360 } 361 }; 362 ArrayList<Node> nodes = new ArrayList<>(ds.getNodes()); 363 for (int i = 0; i < 10; i++) { 364 reader.append(nodes.get(i)); 365 } 366 GuiHelper.runInEDT(() -> { 367 try { 368 reader.parseOsm(NullProgressMonitor.INSTANCE); 369 } catch (ConditionTimeoutException timeoutException) { 370 // This is expected due to the synchronization, so we just swallow it. 371 Logging.trace(timeoutException); 372 } catch (Exception failure) { 373 thrownFailure.set(failure); 374 } finally { 375 parsedData.set(true); 376 } 377 }); 378 // cancel, then continue 379 Awaitility.await().untilAtomic(callCounter, Matchers.greaterThanOrEqualTo(expectedCancelCalls)); 380 reader.cancel(); 381 continueAddition.set(true); 382 Awaitility.await().untilTrue(parsedData); 383 if (thrownFailure.get() != null) { 384 Logging.error(thrownFailure.get()); 385 } 386 assertNull(thrownFailure.get()); 387 assertEquals(expectedCancelCalls, callCounter.get()); 388 } 351 389 }
Note:
See TracChangeset
for help on using the changeset viewer.