Skip to content

Commit b0744bb

Browse files
lavrukovAlexander Lavrukov
andauthored
Refactor TxManager and TxNameGenerator experimental API
Co-authored-by: Alexander Lavrukov <[email protected]>
1 parent e480691 commit b0744bb

File tree

8 files changed

+112
-247
lines changed

8 files changed

+112
-247
lines changed

util/src/main/java/tech/ydb/yoj/util/lang/CallStack.java renamed to repository/src/main/java/tech/ydb/yoj/repository/db/CallStack.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.ydb.yoj.util.lang;
1+
package tech.ydb.yoj.repository.db;
22

33
import java.lang.StackWalker.StackFrame;
44
import java.util.ArrayList;
@@ -11,7 +11,7 @@
1111

1212
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
1313

14-
public final class CallStack {
14+
final class CallStack {
1515
private final ConcurrentMap<FrameKey, Object> mapperCache = new ConcurrentHashMap<>();
1616

1717
public FrameResult findCallingFrame() {

repository/src/main/java/tech/ydb/yoj/repository/db/StdTxManager.java

Lines changed: 26 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,19 @@
1313
import org.slf4j.Logger;
1414
import org.slf4j.LoggerFactory;
1515
import org.slf4j.MDC;
16-
import tech.ydb.yoj.DeprecationWarnings;
1716
import tech.ydb.yoj.repository.db.cache.TransactionLog;
17+
import tech.ydb.yoj.repository.db.exception.QueryInterruptedException;
1818
import tech.ydb.yoj.repository.db.exception.RetryableException;
19-
import tech.ydb.yoj.util.lang.CallStack;
2019
import tech.ydb.yoj.util.lang.Strings;
2120

2221
import javax.annotation.Nullable;
2322
import java.time.Duration;
24-
import java.util.Set;
2523
import java.util.concurrent.atomic.AtomicLong;
2624
import java.util.function.Supplier;
2725

2826
import static java.lang.String.format;
2927
import static java.util.Objects.requireNonNull;
28+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3029
import static tech.ydb.yoj.repository.db.IsolationLevel.ONLINE_CONSISTENT_READ_ONLY;
3130
import static tech.ydb.yoj.repository.db.IsolationLevel.SERIALIZABLE_READ_WRITE;
3231

@@ -45,17 +44,8 @@
4544
*/
4645
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
4746
public final class StdTxManager implements TxManager, TxManagerState {
48-
/**
49-
* @deprecated Please stop using the {@code StdTxManager.useNewTxNameGeneration} field.
50-
* Changing this field has no effect as of YOJ 2.6.1, and it will be <strong>removed completely</strong> in YOJ 2.7.0.
51-
*/
52-
@Deprecated(forRemoval = true)
53-
public static volatile boolean useNewTxNameGeneration = true;
54-
5547
private static final Logger log = LoggerFactory.getLogger(StdTxManager.class);
5648

57-
private static final CallStack callStack = new CallStack();
58-
5949
private static final int DEFAULT_MAX_ATTEMPT_COUNT = 100;
6050
private static final double[] TX_ATTEMPTS_BUCKETS = new double[]
6151
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 25, 35, 40, 45, 50, 60, 70, 80, 90, 100};
@@ -92,19 +82,13 @@ public final class StdTxManager implements TxManager, TxManagerState {
9282
@With(AccessLevel.PRIVATE)
9383
private final int maxAttemptCount;
9484
@With
95-
private final String name;
96-
@With(AccessLevel.PRIVATE)
97-
private final Integer logLine;
98-
@With
9985
@Getter
10086
private final String logContext;
10187
@With(AccessLevel.PRIVATE)
10288
private final TxOptions options;
10389
@With(AccessLevel.PRIVATE)
10490
private final SeparatePolicy separatePolicy;
10591
@With
106-
private final Set<String> skipCallerPackages;
107-
@With
10892
private final TxNameGenerator txNameGenerator;
10993

11094
private final long txLogId = txLogIdSeq.incrementAndGet();
@@ -113,25 +97,16 @@ public StdTxManager(@NonNull Repository repository) {
11397
this(
11498
/* repository */ repository,
11599
/* maxAttemptCount */ DEFAULT_MAX_ATTEMPT_COUNT,
116-
/* name */ null,
117-
/* logLine */ null,
118100
/* logContext */ null,
119101
/* options */ TxOptions.create(SERIALIZABLE_READ_WRITE),
120102
/* separatePolicy */ SeparatePolicy.LOG,
121-
/* skipCallerPackages */ Set.of(),
122-
/* txNameGenerator */ TxNameGenerator.SHORT
103+
/* txNameGenerator */ new TxNameGenerator.Default()
123104
);
124105
}
125106

126-
/**
127-
* @deprecated Constructor is in YOJ 2.x for backwards compatibility, an will be removed in YOJ 3.0.0. Please construct
128-
* {@link #StdTxManager(Repository)} and customize it using the {@code with<...>()} methods instead.
129-
*/
130-
@Deprecated(forRemoval = true)
131-
public StdTxManager(Repository repository, int maxAttemptCount, String name, Integer logLine, String logContext, TxOptions options) {
132-
this(repository, maxAttemptCount, name, logLine, logContext, options, SeparatePolicy.LOG, Set.of(), TxNameGenerator.SHORT);
133-
DeprecationWarnings.warnOnce("StdTxManager(Repository, int, String, Integer, String, TxOptions)",
134-
"Please use the recommended StdTxManager(Repository) constructor and customize the TxManager by using with<...>() methods");
107+
@Override
108+
public StdTxManager withName(String name) {
109+
return withTxNameGenerator(new TxNameGenerator.Constant(name));
135110
}
136111

137112
@Override
@@ -215,25 +190,27 @@ public void tx(Runnable runnable) {
215190

216191
@Override
217192
public <T> T tx(Supplier<T> supplier) {
218-
if (name == null) {
219-
return withGeneratedNameAndLine().tx(supplier);
220-
}
193+
TxName txName = txNameGenerator.generate();
194+
String name = txName.name();
221195

222-
checkSeparatePolicy(separatePolicy, name);
223-
return txImpl(supplier);
224-
}
196+
checkSeparatePolicy(separatePolicy, txName.logName());
225197

226-
private <T> T txImpl(Supplier<T> supplier) {
227198
RetryableException lastRetryableException = null;
228199
TxImpl lastTx = null;
229200
try (Timer ignored = totalDuration.labels(name).startTimer()) {
230201
for (int attempt = 1; attempt <= maxAttemptCount; attempt++) {
231202
try {
232203
attempts.labels(name).observe(attempt);
233204
T result;
234-
try (var ignored1 = attemptDuration.labels(name).startTimer()) {
235-
lastTx = new TxImpl(name, repository.startTransaction(options), options);
236-
result = runAttempt(supplier, lastTx);
205+
try (
206+
var ignored1 = attemptDuration.labels(name).startTimer();
207+
var ignored2 = MDC.putCloseable("tx", formatTx(txName));
208+
var ignored3 = MDC.putCloseable("tx-id", formatTxId());
209+
var ignored4 = MDC.putCloseable("tx-name", txName.logName())
210+
) {
211+
RepositoryTransaction transaction = repository.startTransaction(options);
212+
lastTx = new TxImpl(name, transaction, options);
213+
result = lastTx.run(supplier);
237214
}
238215

239216
if (options.isDryRun()) {
@@ -247,7 +224,12 @@ private <T> T txImpl(Supplier<T> supplier) {
247224
retries.labels(name, getExceptionNameForMetric(e)).inc();
248225
lastRetryableException = e;
249226
if (attempt + 1 <= maxAttemptCount) {
250-
e.sleep(attempt);
227+
try {
228+
MILLISECONDS.sleep(e.getRetryPolicy().calcDuration(attempt).toMillis());
229+
} catch (InterruptedException ex) {
230+
Thread.currentThread().interrupt();
231+
throw new QueryInterruptedException("DB query interrupted", ex);
232+
}
251233
}
252234
} catch (Exception e) {
253235
results.labels(name, "rollback").inc();
@@ -284,49 +266,14 @@ private String getExceptionNameForMetric(RetryableException e) {
284266
return Strings.removeSuffix(e.getClass().getSimpleName(), "Exception");
285267
}
286268

287-
private <T> T runAttempt(Supplier<T> supplier, TxImpl tx) {
288-
try (var ignored2 = MDC.putCloseable("tx", formatTx());
289-
var ignored3 = MDC.putCloseable("tx-id", formatTxId());
290-
var ignored4 = MDC.putCloseable("tx-name", formatTxName(false))) {
291-
return tx.run(supplier);
292-
}
293-
}
294-
295-
private StdTxManager withGeneratedNameAndLine() {
296-
record TxInfo(String name, int lineNumber) {
297-
}
298-
299-
if (!useNewTxNameGeneration) {
300-
DeprecationWarnings.warnOnce("StdTxManager.useNewTxNameGeneration",
301-
"Setting StdTxManager.useNewTxNameGeneration has no effect. Please stop setting this field, it will be removed in YOJ 2.7.0");
302-
}
303-
304-
var info = callStack.findCallingFrame()
305-
.skipPackage(StdTxManager.class.getPackageName())
306-
.skipPackages(skipCallerPackages)
307-
.map(
308-
f -> new TxInfo(
309-
txNameGenerator.nameFor(f.getClassName(), f.getMethodName()),
310-
f.getLineNumber()
311-
),
312-
txNameGenerator
313-
);
314-
315-
return withName(info.name).withLogLine(info.lineNumber);
316-
}
317-
318-
private String formatTx() {
319-
return formatTxId() + " {" + formatTxName(true) + "}";
269+
private String formatTx(TxName txName) {
270+
return formatTxId() + " {" + txName.logName() + (logContext != null ? "/" + logContext : "") + "}";
320271
}
321272

322273
private String formatTxId() {
323274
return Strings.leftPad(Long.toUnsignedString(txLogId, 36), 6, '0') + options.getIsolationLevel().getTxIdSuffix();
324275
}
325276

326-
private String formatTxName(boolean withContext) {
327-
return name + (logLine != null ? ":" + logLine : "") + (withContext && logContext != null ? "/" + logContext : "");
328-
}
329-
330277
@Override
331278
public TxManagerState getState() {
332279
return this;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tech.ydb.yoj.repository.db;
2+
3+
public record TxName(
4+
String name,
5+
String logName
6+
) {
7+
}
Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
package tech.ydb.yoj.repository.db;
22

3-
import lombok.NonNull;
3+
import tech.ydb.yoj.ExperimentalApi;
44

5+
import java.util.Set;
56
import java.util.regex.Pattern;
67

78
/**
89
* Generates default name for a transaction, depending on the caller class and caller method names.
910
*/
11+
@ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/188")
1012
public interface TxNameGenerator {
1113
/**
12-
* @param className caller class name
13-
* @param methodName caller method name
1414
* @return transaction name
1515
*/
16-
@NonNull
17-
String nameFor(@NonNull String className, @NonNull String methodName);
16+
TxName generate();
1817

1918
/**
2019
* Generates short tx names of the form {@code ClNam#meNa} (from {@code package.name.ClassName[$InnerClassName]} and {@code methodName}).
@@ -25,76 +24,82 @@ public interface TxNameGenerator {
2524
* <strong>The disadvantage is that short tx names are not particularly human-readable.</strong>
2625
*
2726
* <p>This is the classic YOJ default tx name generator, used since YOJ 1.0.0.
28-
*
29-
* @see #LONG
30-
* @see #NONE
3127
*/
32-
TxNameGenerator SHORT = new TxNameGenerator() {
28+
final class Default implements TxNameGenerator {
3329
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".*\\.");
3430
private static final Pattern INNER_CLASS_PATTERN_CLEAR = Pattern.compile("\\$.*");
3531
private static final Pattern SHORTEN_NAME_PATTERN = Pattern.compile("([A-Z][a-z]{2})[a-z]+");
32+
private static final CallStack callStack = new CallStack();
3633

37-
@NonNull
38-
@Override
39-
public String nameFor(@NonNull String className, @NonNull String methodName) {
40-
var cn = replaceFirst(className, PACKAGE_PATTERN, "");
41-
cn = replaceFirst(cn, INNER_CLASS_PATTERN_CLEAR, "");
42-
cn = replaceAll(cn, SHORTEN_NAME_PATTERN, "$1");
43-
var mn = replaceAll(methodName, SHORTEN_NAME_PATTERN, "$1");
44-
return cn + '#' + mn;
34+
private final Set<String> packagesToSkip;
35+
36+
public Default(String... packagesToSkip) {
37+
this.packagesToSkip = Set.of(packagesToSkip);
4538
}
4639

4740
@Override
48-
public String toString() {
49-
return "TxNameGenerator.SHORT";
41+
public TxName generate() {
42+
var stack = getStackResult(callStack, packagesToSkip);
43+
44+
return stack.map(frame -> {
45+
String className = PACKAGE_PATTERN.matcher(frame.getClassName()).replaceFirst("");
46+
className = INNER_CLASS_PATTERN_CLEAR.matcher(className).replaceFirst("");
47+
className = SHORTEN_NAME_PATTERN.matcher(className).replaceAll("$1");
48+
String mn = SHORTEN_NAME_PATTERN.matcher(frame.getMethodName()).replaceAll("$1");
49+
String name = className + '#' + mn;
50+
return buildDefaultTxInfo(name, frame.getLineNumber());
51+
});
5052
}
51-
};
53+
}
5254

5355
/**
5456
* Generates long transaction names of the form {@code ClassName.methodName[$InnerClassName]}
5557
* (from {@code package.name.ClassName[$InnerClassName]} and {@code methodName}).
5658
* Inner class names, including anonymous class names, are kept in the {@code Class.getName()} format ({@code $<inner class name>}).
57-
*
58-
* @see #SHORT
59-
* @see #NONE
6059
*/
61-
TxNameGenerator LONG = new TxNameGenerator() {
60+
final class Long implements TxNameGenerator {
6261
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".*\\.");
62+
private static final CallStack callStack = new CallStack();
6363

64-
@NonNull
65-
@Override
66-
public String nameFor(@NonNull String className, @NonNull String methodName) {
67-
var cn = replaceFirst(className, PACKAGE_PATTERN, "");
68-
return cn + '.' + methodName;
69-
}
64+
private final Set<String> packagesToSkip;
7065

71-
@Override
72-
public String toString() {
73-
return "TxNameGenerator.LONG";
66+
public Long(String... packagesToSkip) {
67+
this.packagesToSkip = Set.of(packagesToSkip);
7468
}
75-
};
7669

77-
/**
78-
* Prohibits starting transactions without explicitly setting transaction name via {@link TxManager#withName(String)}.
79-
*/
80-
TxNameGenerator NONE = new TxNameGenerator() {
81-
@NonNull
8270
@Override
83-
public String nameFor(@NonNull String className, @NonNull String methodName) {
84-
throw new IllegalStateException("Transaction name must be explicitly set via TxManager.withName()");
85-
}
71+
public TxName generate() {
72+
var stack = getStackResult(callStack, packagesToSkip);
8673

87-
@Override
88-
public String toString() {
89-
return "TxNameGenerator.NONE";
74+
return stack.map(frame -> {
75+
String className = PACKAGE_PATTERN.matcher(frame.getClassName()).replaceFirst("");
76+
String name = className + '.' + frame.getMethodName();
77+
return buildDefaultTxInfo(name, frame.getLineNumber());
78+
});
9079
}
91-
};
80+
}
81+
82+
private static TxName buildDefaultTxInfo(String name, int lineNumber) {
83+
String logName = name + ":" + lineNumber;
84+
return new TxName(name, logName);
85+
}
9286

93-
private static String replaceFirst(String input, Pattern regex, String replacement) {
94-
return regex.matcher(input).replaceFirst(replacement);
87+
private static CallStack.FrameResult getStackResult(CallStack callStack, Set<String> packagesToSkip) {
88+
return callStack.findCallingFrame()
89+
.skipPackage(StdTxManager.class.getPackageName())
90+
.skipPackages(packagesToSkip);
9591
}
9692

97-
private static String replaceAll(String input, Pattern regex, String replacement) {
98-
return regex.matcher(input).replaceAll(replacement);
93+
final class Constant implements TxNameGenerator {
94+
private final TxName txName;
95+
96+
public Constant(String name) {
97+
this.txName = new TxName(name, name);
98+
}
99+
100+
@Override
101+
public TxName generate() {
102+
return txName;
103+
}
99104
}
100105
}

0 commit comments

Comments
 (0)