jsr354-ri
jsr354-ri copied to clipboard
Under concurrency, Moneta formatting fails.
Bringing the discussion from Gitter to here, as I was asked to implement a test case without frameworks, just raw moneta. Below is my pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.4.2</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<finalName>service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.example.test.Application</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
And my class:
package com.example.test;
import org.javamoney.moneta.Money;
import javax.money.MonetaryException;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
public class Application {
private static final MonetaryAmountFormat MONEY_FORMATTER = MonetaryFormats.getAmountFormat(Locale.ENGLISH);
public static void main(final String[] args) throws InterruptedException {
final ExecutorService pool = Executors.newWorkStealingPool(100);
final List<Callable<Boolean>> taskList = new ArrayList<>();
for (int i = 0; i < 500000; i++) {
final Integer startBidAmount = ThreadLocalRandom.current().nextInt(2, 70) * 1000;
final Money startBidMoney = Money.of(startBidAmount, "CAD");
final String money = MONEY_FORMATTER.format(startBidMoney);
taskList.add(() -> {
try {
Money.parse(money, MONEY_FORMATTER);
} catch (MonetaryException e) {
System.out.println("Parsing money: " + money);
System.out.println(e.getMessage());
}
return true;
});
}
pool.invokeAll(taskList);
pool.shutdown();
}
}
Code can be executed running:
sdk use java 11.0.7-amzn # Also reproducible on 11.0.7.hs-adpt
mvn clean install
java -jar target/service-jar-with-dependencies.jar
All reported variety of exceptions are now being reported. Below is an example of exceptions generated:
Parsing money: CAD28,000.00
Could not parse CurrencyUnit. Unknown currency code:
Aug. 18, 2020 10:42:55 A.M. org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat parse
INFO: Failed to parse positive pattern, trying negative for: CAD45,000.00
java.lang.NumberFormatException: For input string: "45.0045E05"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:692)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2121)
at org.javamoney.moneta.spi.format.AmountNumberToken.parseToken(AmountNumberToken.java:184)
at org.javamoney.moneta.spi.format.AmountNumberToken.parse(AmountNumberToken.java:155)
at org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat.parse(DefaultMonetaryAmountFormat.java:174)
at org.javamoney.moneta.Money.parse(Money.java:883)
at com.eblock.simulcast.test.Application.lambda$main$0(Application.java:33)
at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Aug. 18, 2020 10:42:55 A.M. org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat parse
INFO: Failed to parse positive pattern, trying negative for: CAD46,000.00
java.lang.NumberFormatException: For input string: "4060.0"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:692)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2121)
at org.javamoney.moneta.spi.format.AmountNumberToken.parseToken(AmountNumberToken.java:184)
at org.javamoney.moneta.spi.format.AmountNumberToken.parse(AmountNumberToken.java:155)
at org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat.parse(DefaultMonetaryAmountFormat.java:174)
at org.javamoney.moneta.Money.parse(Money.java:883)
at com.eblock.simulcast.test.Application.lambda$main$0(Application.java:33)
at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Parsing money: CAD15,000.00
Could not parse CurrencyUnit. Unknown currency code:
Parsing money: CAD45,000.00
Could not parse CurrencyUnit. Unknown currency code:
Parsing money: CAD27,000.00
Could not parse CurrencyUnit. Unknown currency code:
Aug. 18, 2020 10:42:55 A.M. org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat parse
INFO: Failed to parse positive pattern, trying negative for: CAD25,000.00
java.lang.NumberFormatException: multiple points
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:543)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2126)
at org.javamoney.moneta.spi.format.AmountNumberToken.parseToken(AmountNumberToken.java:184)
at org.javamoney.moneta.spi.format.AmountNumberToken.parse(AmountNumberToken.java:155)
at org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat.parse(DefaultMonetaryAmountFormat.java:174)
at org.javamoney.moneta.Money.parse(Money.java:883)
at com.eblock.simulcast.test.Application.lambda$main$0(Application.java:33)
at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Aug. 18, 2020 10:42:55 A.M. org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat parse
INFO: Failed to parse positive pattern, trying negative for: CAD38,000.00
java.lang.NumberFormatException: For input string: ".380380E05"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:678)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2121)
at org.javamoney.moneta.spi.format.AmountNumberToken.parseToken(AmountNumberToken.java:184)
at org.javamoney.moneta.spi.format.AmountNumberToken.parse(AmountNumberToken.java:155)
at org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat.parse(DefaultMonetaryAmountFormat.java:174)
at org.javamoney.moneta.Money.parse(Money.java:883)
at com.eblock.simulcast.test.Application.lambda$main$0(Application.java:33)
at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Aug. 18, 2020 10:42:55 A.M. org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat parse
INFO: Failed to parse positive pattern, trying negative for: CAD61,000.00
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:702)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2121)
at org.javamoney.moneta.spi.format.AmountNumberToken.parseToken(AmountNumberToken.java:184)
at org.javamoney.moneta.spi.format.AmountNumberToken.parse(AmountNumberToken.java:155)
at org.javamoney.moneta.spi.format.DefaultMonetaryAmountFormat.parse(DefaultMonetaryAmountFormat.java:174)
at org.javamoney.moneta.Money.parse(Money.java:883)
at com.eblock.simulcast.test.Application.lambda$main$0(Application.java:33)
at java.base/java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1448)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
I'm not yet sure if it's only due to concurrency, but if the same works in a single thread it might.
I'm not yet sure if it's only due to concurrency, but if the same works in a single thread it might.
I've commented out the Executor related portion and left only the loop executing the commands. Everything works perfectly fine.
At least the number parts use NumberFormat which as java.text.Format in general are not thread-safe. Approaches like http://blog.vityuk.com/2011/03/java-formatters-best-practices.html might help, but it remains to be seen, if we can do this with a simple update release like 1.4.3 or 1.4.4 of Moneta or it could require a new JSR (which is also planned, but probably not started before early 2021)
Is this still valid? The MonetaryAmountFormat docs do state that
Instances of this class are not required to be thread-safe
so changing this would require an update to the JSR, right?
Not sure, if there was ever a PR for this, but the JavaDoc says it is not required to be thread-safe, it could still be thread-safe, that's optional. Closing it for now, if someone needs to revisit it, please open a new ticket.