Skip to content

Commit e80f520

Browse files
authored
Merge pull request #3855 from volodya-lombrozo/3840_tr_stepped
feat(#3840): Fix Concurrency Issue Related to `TrStepped`
2 parents a0c3048 + 6a85eb9 commit e80f520

2 files changed

Lines changed: 163 additions & 6 deletions

File tree

eo-parser/src/main/java/org/eolang/parser/TrStepped.java

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import com.yegor256.xsline.TrEnvelope;
3232
import com.yegor256.xsline.TrLambda;
3333
import com.yegor256.xsline.Train;
34+
import java.util.concurrent.CountDownLatch;
3435
import org.cactoos.Scalar;
3536
import org.cactoos.io.ResourceOf;
3637
import org.cactoos.scalar.Sticky;
38+
import org.cactoos.scalar.Synced;
3739
import org.cactoos.text.TextOf;
3840

3941
/**
40-
* Trains that adds sheet names that were processed.
42+
* Train that adds sheet names that were processed.
4143
*
4244
* @since 0.1
4345
*/
@@ -47,26 +49,39 @@ final class TrStepped extends TrEnvelope {
4749
* Apply changes to each XML after processing.
4850
*/
4951
private static final Scalar<XSL> STEPPED = new Sticky<>(
50-
() -> new XSLDocument(
51-
new TextOf(
52-
new ResourceOf("org/eolang/parser/_stepped.xsl")
53-
).asString()
52+
new Once<XSL>(
53+
() -> new XSLDocument(
54+
new TextOf(
55+
new ResourceOf("org/eolang/parser/_stepped.xsl")
56+
).asString()
57+
)
5458
)
5559
);
5660

5761
/**
5862
* Ctor.
63+
*
5964
* @param train Original train
6065
*/
6166
TrStepped(final Train<Shift> train) {
67+
this(train, TrStepped.STEPPED);
68+
}
69+
70+
/**
71+
* Ctor.
72+
*
73+
* @param train Original train
74+
* @param stepped XSL to apply
75+
*/
76+
TrStepped(final Train<Shift> train, final Scalar<XSL> stepped) {
6277
super(
6378
new TrLambda(
6479
train,
6580
shift -> new StAfter(
6681
shift,
6782
new StLambda(
6883
shift::uid,
69-
(pos, xml) -> TrStepped.STEPPED.value()
84+
(pos, xml) -> new Synced<>(stepped).value()
7085
.with("step", pos)
7186
.with("sheet", shift.uid())
7287
.transform(xml)
@@ -75,4 +90,54 @@ final class TrStepped extends TrEnvelope {
7590
)
7691
);
7792
}
93+
94+
/**
95+
* Scalar that loads the value only once.
96+
*
97+
* @param <T> Type of the value
98+
* @since 0.51
99+
*/
100+
static final class Once<T> implements Scalar<T> {
101+
102+
/**
103+
* Origin scalar.
104+
*/
105+
private final Scalar<T> origin;
106+
107+
/**
108+
* Latch to count down.
109+
*/
110+
private final CountDownLatch latch;
111+
112+
/**
113+
* Ctor.
114+
*
115+
* @param origin Origin scalar
116+
*/
117+
Once(final Scalar<T> origin) {
118+
this(origin, new CountDownLatch(1));
119+
}
120+
121+
/**
122+
* Ctor.
123+
*
124+
* @param origin Origin scalar
125+
* @param latch Latch to count down
126+
*/
127+
private Once(final Scalar<T> origin, final CountDownLatch latch) {
128+
this.origin = origin;
129+
this.latch = latch;
130+
}
131+
132+
@Override
133+
public T value() throws Exception {
134+
if (this.latch.getCount() < 1) {
135+
throw new IllegalStateException(
136+
String.format("Resource '%s' should be loaded only once", this.origin)
137+
);
138+
}
139+
this.latch.countDown();
140+
return this.origin.value();
141+
}
142+
}
78143
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016-2025 Objectionary.com
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included
14+
* in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package org.eolang.parser;
25+
26+
import com.jcabi.matchers.XhtmlMatchers;
27+
import com.jcabi.xml.XML;
28+
import com.jcabi.xml.XMLDocument;
29+
import com.jcabi.xml.XSL;
30+
import com.jcabi.xml.XSLDocument;
31+
import com.yegor256.Together;
32+
import com.yegor256.xsline.Shift;
33+
import com.yegor256.xsline.StClasspath;
34+
import com.yegor256.xsline.TrDefault;
35+
import com.yegor256.xsline.Xsline;
36+
import org.cactoos.io.ResourceOf;
37+
import org.cactoos.scalar.Sticky;
38+
import org.cactoos.text.TextOf;
39+
import org.hamcrest.MatcherAssert;
40+
import org.junit.jupiter.api.RepeatedTest;
41+
import org.junit.jupiter.api.Test;
42+
43+
/**
44+
* Test cases for {@link TrStepped}.
45+
*
46+
* @since 0.51
47+
*/
48+
final class TrSteppedTest {
49+
50+
@Test
51+
void addsSheetName() {
52+
MatcherAssert.assertThat(
53+
"We expect the sheet name to be added",
54+
new Xsline(
55+
new TrStepped(
56+
new TrDefault<Shift>().with(
57+
new StClasspath("/org/eolang/parser/print/wrap-data.xsl")
58+
)
59+
)
60+
).pass(new XMLDocument("<program><concurrency>no</concurrency></program>")).toString(),
61+
XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']")
62+
);
63+
}
64+
65+
@RepeatedTest(10)
66+
void addsSheetNameConcurrently() {
67+
final XML doc = new XMLDocument("<program><concurrency>yes</concurrency></program>");
68+
final Sticky<XSL> loading = new Sticky<>(
69+
new TrStepped.Once<XSL>(
70+
() -> new XSLDocument(
71+
new TextOf(
72+
() -> new ResourceOf("org/eolang/parser/_stepped.xsl").stream()
73+
).asString()
74+
)
75+
)
76+
);
77+
MatcherAssert.assertThat(
78+
"We expect the sheet name to be added successfully in concurrent environment",
79+
new Together<>(
80+
i -> new Xsline(
81+
new TrStepped(
82+
new TrDefault<Shift>().with(
83+
new StClasspath("/org/eolang/parser/print/wrap-data.xsl")
84+
),
85+
loading
86+
)
87+
).pass(doc).toString()
88+
).iterator().next(),
89+
XhtmlMatchers.hasXPath("/program/sheets/sheet[text()='wrap-data']")
90+
);
91+
}
92+
}

0 commit comments

Comments
 (0)