Skip to content

Commit 753e4bc

Browse files
author
Alexander Lavrukov
committed
refactoring TxNameGenerator
1 parent 65b6f5b commit 753e4bc

File tree

8 files changed

+108
-248
lines changed

8 files changed

+108
-248
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: 27 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
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;
@@ -25,8 +24,8 @@
2524
import java.util.concurrent.atomic.AtomicLong;
2625
import java.util.function.Supplier;
2726

28-
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(Set.of())
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.Simple(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+
TxInfo txInfo = txNameGenerator.generate();
194+
String name = txInfo.name();
221195

222-
checkSeparatePolicy(separatePolicy, name);
223-
return txImpl(supplier);
224-
}
196+
checkSeparatePolicy(separatePolicy, txInfo.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+
RepositoryTransaction transaction = repository.startTransaction(options);
206+
lastTx = new TxImpl(name, transaction, options);
207+
try (
208+
var ignored1 = attemptDuration.labels(name).startTimer();
209+
var ignored2 = MDC.putCloseable("tx", formatTx(txInfo));
210+
var ignored3 = MDC.putCloseable("tx-id", formatTxId());
211+
var ignored4 = MDC.putCloseable("tx-name", txInfo.logName())
212+
) {
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();
@@ -273,7 +255,7 @@ private static void checkSeparatePolicy(SeparatePolicy separatePolicy, String tx
273255
case ALLOW -> {
274256
}
275257
case STRICT ->
276-
throw new IllegalStateException(format("Transaction %s was run when another transaction is active", txName));
258+
throw new IllegalStateException("Transaction was run when another transaction is active");
277259
case LOG ->
278260
log.warn("Transaction '{}' was run when another transaction is active. Perhaps unexpected behavior. " +
279261
"Use TxManager.separate() to avoid this message", txName);
@@ -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(TxInfo txInfo) {
270+
return formatTxId() + " {" + txInfo.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 TxInfo(
4+
String name,
5+
String logName
6+
) {
7+
}
Lines changed: 50 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package tech.ydb.yoj.repository.db;
22

3-
import lombok.NonNull;
3+
import lombok.RequiredArgsConstructor;
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
*/
1011
public interface TxNameGenerator {
1112
/**
12-
* @param className caller class name
13-
* @param methodName caller method name
1413
* @return transaction name
1514
*/
16-
@NonNull
17-
String nameFor(@NonNull String className, @NonNull String methodName);
15+
TxInfo generate();
1816

1917
/**
2018
* Generates short tx names of the form {@code ClNam#meNa} (from {@code package.name.ClassName[$InnerClassName]} and {@code methodName}).
@@ -25,76 +23,76 @@ public interface TxNameGenerator {
2523
* <strong>The disadvantage is that short tx names are not particularly human-readable.</strong>
2624
*
2725
* <p>This is the classic YOJ default tx name generator, used since YOJ 1.0.0.
28-
*
29-
* @see #LONG
30-
* @see #NONE
3126
*/
32-
TxNameGenerator SHORT = new TxNameGenerator() {
27+
@RequiredArgsConstructor
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;
45-
}
34+
private final Set<String> packagesToSkip;
4635

4736
@Override
48-
public String toString() {
49-
return "TxNameGenerator.SHORT";
37+
public TxInfo generate() {
38+
var stack = getStackResult(callStack, packagesToSkip);
39+
40+
return stack.map(frame -> {
41+
String className = PACKAGE_PATTERN.matcher(frame.getClassName()).replaceFirst("");
42+
className = INNER_CLASS_PATTERN_CLEAR.matcher(className).replaceFirst("");
43+
className = SHORTEN_NAME_PATTERN.matcher(className).replaceAll("$1");
44+
String mn = SHORTEN_NAME_PATTERN.matcher(frame.getMethodName()).replaceAll("$1");
45+
String name = className + '#' + mn;
46+
return buildDefaultTxInfo(name, frame.getLineNumber());
47+
});
5048
}
51-
};
49+
}
5250

5351
/**
5452
* Generates long transaction names of the form {@code ClassName.methodName[$InnerClassName]}
5553
* (from {@code package.name.ClassName[$InnerClassName]} and {@code methodName}).
5654
* 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
6055
*/
61-
TxNameGenerator LONG = new TxNameGenerator() {
56+
@RequiredArgsConstructor
57+
final class Long implements TxNameGenerator {
6258
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".*\\.");
59+
private static final CallStack callStack = new CallStack();
6360

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-
}
61+
private final Set<String> packagesToSkip;
7062

7163
@Override
72-
public String toString() {
73-
return "TxNameGenerator.LONG";
74-
}
75-
};
64+
public TxInfo generate() {
65+
var stack = getStackResult(callStack, packagesToSkip);
7666

77-
/**
78-
* Prohibits starting transactions without explicitly setting transaction name via {@link TxManager#withName(String)}.
79-
*/
80-
TxNameGenerator NONE = new TxNameGenerator() {
81-
@NonNull
82-
@Override
83-
public String nameFor(@NonNull String className, @NonNull String methodName) {
84-
throw new IllegalStateException("Transaction name must be explicitly set via TxManager.withName()");
67+
return stack.map(frame -> {
68+
String className = PACKAGE_PATTERN.matcher(frame.getClassName()).replaceFirst("");
69+
String name = className + '.' + frame.getMethodName();
70+
return buildDefaultTxInfo(name, frame.getLineNumber());
71+
});
8572
}
73+
}
8674

87-
@Override
88-
public String toString() {
89-
return "TxNameGenerator.NONE";
90-
}
91-
};
75+
private static TxInfo buildDefaultTxInfo(String name, int lineNumber) {
76+
String logName = name + ":" + lineNumber;
77+
return new TxInfo(name, logName);
78+
}
9279

93-
private static String replaceFirst(String input, Pattern regex, String replacement) {
94-
return regex.matcher(input).replaceFirst(replacement);
80+
private static CallStack.FrameResult getStackResult(CallStack callStack, Set<String> packagesToSkip) {
81+
return callStack.findCallingFrame()
82+
.skipPackage(StdTxManager.class.getPackageName())
83+
.skipPackages(packagesToSkip);
9584
}
9685

97-
private static String replaceAll(String input, Pattern regex, String replacement) {
98-
return regex.matcher(input).replaceAll(replacement);
86+
final class Simple implements TxNameGenerator {
87+
private final TxInfo txInfo;
88+
89+
public Simple(String name) {
90+
this.txInfo = new TxInfo(name, name);
91+
}
92+
93+
@Override
94+
public TxInfo generate() {
95+
return txInfo;
96+
}
9997
}
10098
}

0 commit comments

Comments
 (0)