Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/main/java/com/github/_1c_syntax/utils/Lazy.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ public T getOrCompute(Supplier<T> supplier) {
final T result = value; // Just one volatile read
if (result == null) {
lock.lock();
var localResult = maybeCompute(supplier);
lock.unlock();
return localResult;
try {
return maybeCompute(supplier);
} finally {
lock.unlock();
}
}
return result;
}
Expand Down
113 changes: 113 additions & 0 deletions src/test/java/com/github/_1c_syntax/utils/LazyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* This file is a part of 1c-syntax utils.
*
* Copyright (c) 2018-2026
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* 1c-syntax utils is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* 1c-syntax utils is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with 1c-syntax utils.
*/
package com.github._1c_syntax.utils;

import org.junit.jupiter.api.Test;

import java.util.concurrent.locks.ReentrantLock;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class LazyTest {

@Test
void getOrComputeReturnsValue() {
var lazy = new Lazy<>(() -> "value");
assertThat(lazy.getOrCompute()).isEqualTo("value");
assertThat(lazy.isPresent()).isTrue();
}

@Test
void getOrComputeCachesValue() {
var counter = new int[]{0};
var lazy = new Lazy<>(() -> {
counter[0]++;
return "value";
});
lazy.getOrCompute();
lazy.getOrCompute();
lazy.getOrCompute();
assertThat(counter[0]).isEqualTo(1);
}

@Test
void supplierExceptionReleasesLock() {
// Регрессия: до фикса unlock() стоял после maybeCompute() без try/finally,
// и любое RuntimeException из supplier'а оставляло lock захваченным
// навсегда — следующий поток вешал getOrCompute() либо документ-сервис
// (где Lazy шарит lock с externalным ReentrantLock).
var lock = new ReentrantLock();
var lazy = new Lazy<String>(() -> {
throw new IllegalStateException("boom");
}, lock);

assertThatThrownBy(lazy::getOrCompute).isInstanceOf(IllegalStateException.class);
assertThat(lock.isLocked())
.as("lock должен быть отпущен после RuntimeException в supplier'е")
.isFalse();
assertThat(lock.getHoldCount()).isZero();
}

@Test
void supplierExceptionReleasesSharedLockMultipleAttempts() {
// Дважды бросаем, потом успешно вычисляем — lock должен корректно
// освобождаться каждый раз, и финальный getOrCompute должен пройти.
var lock = new ReentrantLock();
var attempt = new int[]{0};
var lazy = new Lazy<String>(() -> {
attempt[0]++;
if (attempt[0] < 3) {
throw new IllegalStateException("attempt " + attempt[0]);
}
return "ok";
}, lock);

assertThatThrownBy(lazy::getOrCompute).isInstanceOf(IllegalStateException.class);
assertThat(lock.isLocked()).isFalse();
assertThatThrownBy(lazy::getOrCompute).isInstanceOf(IllegalStateException.class);
assertThat(lock.isLocked()).isFalse();
assertThat(lazy.getOrCompute()).isEqualTo("ok");
assertThat(lock.isLocked()).isFalse();
}

@Test
void clearAllowsRecompute() {
var counter = new int[]{0};
var lazy = new Lazy<>(() -> {
counter[0]++;
return "v" + counter[0];
});
assertThat(lazy.getOrCompute()).isEqualTo("v1");
lazy.clear();
assertThat(lazy.isPresent()).isFalse();
assertThat(lazy.getOrCompute()).isEqualTo("v2");
}

@Test
void getReturnsNullBeforeCompute() {
var lazy = new Lazy<>(() -> "value");
assertThat(lazy.get()).isNull();
lazy.getOrCompute();
assertThat(lazy.get()).isEqualTo("value");
}
}
Loading