Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 5 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ gitVersioning.apply {
refs {
describeTagFirstParent = false
tag("v(?<tagVersion>[0-9].*)") {
version = $$"${ref.tagVersion}${dirty}"
version = "\${ref.tagVersion}\${dirty}"
}

branch("develop") {
version = $$"${describe.tag.version}." +
$$"${describe.distance}-SNAPSHOT${dirty}"
version = "\${describe.tag.version}." +
"\${describe.distance}-SNAPSHOT\${dirty}"
}
Comment thread
nixel2007 marked this conversation as resolved.

branch(".+") {
version = $$"${ref}-${commit.short}${dirty}"
version = "\${ref}-\${commit.short}\${dirty}"
}
}

rev {
version = $$"${commit.short}${dirty}"
version = "\${commit.short}\${dirty}"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,17 +241,41 @@ private void publishEvent(ApplicationEvent event) {
LOGGER.warn("Trying to send event in not active event publisher.");
return;
}
// Если установлен workspace-контекст, событие принадлежит конкретному
// workspace и должно идти только в Spring-контекст-владельца этого workspace.
var serverContext = extractServerContext(event);
var workspaceUri = serverContext == null ? null : serverContext.getWorkspaceUri();
if (workspaceUri == null || WorkspaceContextHolder.get() != null) {
dispatchEvent(contexts, serverContext, event);
return;
}
// На текущем потоке workspace-контекст не установлен (например, didClose приходит
// с потока LSP4J без WorkspaceContextHolder) — восполняем его из workspace источника
// события на время рассылки, иначе workspace-scoped @EventListener-бины (индексы
// ссылок/типов и т.п.) падают ScopeNotActiveException и теряют событие. Уже
// установленный контекст не переопределяем: вызывающий код (и тестовая
// инфраструктура) выставляет его осознанно.
WorkspaceContextHolder.run(
workspaceUri,
WorkspaceContextHolder.nameForUri(workspaceUri),
() -> dispatchEvent(contexts, serverContext, event)
);
}

private void dispatchEvent(
ApplicationContext[] contexts,
@Nullable ServerContext serverContext,
ApplicationEvent event
) {
// Если определяется владелец, событие принадлежит конкретному workspace и должно
// идти только в Spring-контекст-владельца этого workspace.
// В тестах это критично: несколько Spring-контекстов с разными @SpringBootTest-
// конфигурациями висят в TestContext-кэше и зарегистрированы в JVM-singleton
// аспекте. Если рассылать во ВСЕ, listener'ы non-owning контекстов создают
// workspace-scoped beans под текущий WSCH-URI и затрагивают чужое состояние —
// в боевом сценарии это маловероятно (один LS = один контекст), но в тестах
// ломает соседние тест-классы. Когда WSCH не установлен (глобальные события
// ломает соседние тест-классы. Когда владелец не определяется (глобальные события
// вроде {@link GlobalLanguageServerConfigurationChangedEvent} или Initialize),
// рассылаем во все — у них нет workspace-привязки.
var owner = findOwningContext(contexts, event);
var owner = findOwningContext(contexts, serverContext);
if (owner != null) {
try {
owner.publishEvent(event);
Expand All @@ -278,9 +302,8 @@ private void publishEvent(ApplicationEvent event) {
* если владелец не определяется (тогда вызывающий рассылает во все контексты).
*/
private static @Nullable ApplicationContext findOwningContext(
ApplicationContext[] contexts, ApplicationEvent event
ApplicationContext[] contexts, @Nullable ServerContext serverContext
) {
var serverContext = extractServerContext(event);
if (serverContext != null) {
for (var ctx : contexts) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public ServerContext addWorkspace(URI workspaceUri, @Nullable String workspaceNa

var rootPath = Absolute.path(workspaceUri);

var name = workspaceName != null ? workspaceName : extractWorkspaceName(workspaceUri);
var name = workspaceName != null ? workspaceName : WorkspaceContextHolder.nameForUri(workspaceUri);
WorkspaceContextHolder.registerWorkspace(workspaceUri, name);

// Set workspace context for scoped bean resolution
Expand Down Expand Up @@ -219,7 +219,7 @@ public void removeWorkspace(WorkspaceFolder workspaceFolder) {
// Two-arg forUri используем, чтобы не требовать наличие URI в WORKSPACE_NAMES
// (для async-propagated workspace'ов запись там может отсутствовать).
if (serverContext != null) {
var name = extractWorkspaceName(uri);
var name = WorkspaceContextHolder.nameForUri(uri);
try (var ctx = WorkspaceContextHolder.forUri(uri, name)) {
serverContext.clear();
}
Expand Down Expand Up @@ -463,16 +463,4 @@ private void repointPrimaryBeforeRemoval(URI removedUri) {
primaryWorkspaceUri.set(next);
}

private static String extractWorkspaceName(URI workspaceUri) {
var path = workspaceUri.getPath();
if (path == null) {
return workspaceUri.toString();
}
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
var lastSlash = path.lastIndexOf('/');
return lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ public static boolean isRegistered(URI workspaceUri) {
return workspaceUri != null && WORKSPACE_NAMES.containsKey(workspaceUri);
}

/**
* Имя workspace для URI: зарегистрированное через {@link #registerWorkspace(URI, String)},
* а если workspace не зарегистрирован — последний сегмент пути URI.
* <p>
* В отличие от {@link #set(URI)} не требует предварительной регистрации, поэтому
* подходит для установки контекста на «чужих» потоках (async-propagated workspace'ы
* могут отсутствовать в реестре имён).
*
* @param workspaceUri URI workspace
* @return имя workspace
*/
public static String nameForUri(URI workspaceUri) {
var name = WORKSPACE_NAMES.get(workspaceUri);
return name != null ? name : deriveNameFromUri(workspaceUri);
}

private static String deriveNameFromUri(URI workspaceUri) {
var path = workspaceUri.getPath();
if (path == null) {
return workspaceUri.toString();
}
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
var lastSlash = path.lastIndexOf('/');
return lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
}

/**
* Создать AutoCloseable-контекст workspace с URI и именем.
* При закрытии восстанавливает предыдущее значение ThreadLocal.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
import com.github._1c_syntax.bsl.languageserver.context.events.ServerContextDocumentClearedEvent;
import com.github._1c_syntax.bsl.languageserver.context.events.ServerContextDocumentClosedEvent;
import com.github._1c_syntax.bsl.languageserver.context.events.ServerContextDocumentRemovedEvent;
import com.github._1c_syntax.bsl.languageserver.infrastructure.WorkspaceContextHolder;
import com.github._1c_syntax.bsl.languageserver.util.TestUtils;
import com.github._1c_syntax.bsl.parser.BSLParser;
import com.github._1c_syntax.utils.Absolute;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -92,6 +94,40 @@ void aggregatesMultipleCallsForSameReceiver() {
assertThat(index.byReceiver(uri, ast, "ТЗ")).hasSize(2);
}

@Test
void clearsOnCloseFromThreadWithoutWorkspaceContext() throws InterruptedException {
// given — документ с ресивером ТЗ; запросы к индексу идут в workspace документа.
var documentContext = TestUtils.getDocumentContext("""
Процедура Тест()
ТЗ.Колонки.Добавить("Имя");
КонецПроцедуры
""");
var uri = documentContext.getUri();
var serverContext = documentContext.getServerContext();
WorkspaceContextHolder.set(serverContext.getWorkspaceUri());

assertThat(index.byReceiver(uri, documentContext.getAst(), "ТЗ")).hasSize(1);

// второй документ без ТЗ — его AST играет роль «свежего» дерева первого после закрытия.
var otherUri = Absolute.path("src/test/resources/empty-workspace/fake-uri-other.bsl").toUri();
var otherDocument = TestUtils.getDocumentContext(otherUri, """
Процедура Тест()
Сообщить("без ресивера");
КонецПроцедуры
""", serverContext);
var freshAst = otherDocument.getAst();

// when — документ закрывается с потока без workspace-контекста
// (так textDocument/didClose приходит с потока LSP4J).
var closer = new Thread(() -> serverContext.closeDocument(documentContext));
closer.start();
closer.join();

// then — событие закрытия дошло до workspace-scoped слушателя: индекс по URI сброшен
// и пересобирается по переданному AST, а не отдаёт узлы старого дерева.
assertThat(index.byReceiver(uri, freshAst, "ТЗ")).isEmpty();
}

private void assertAllReceivers(URI uri, BSLParser.FileContext ast) {
assertThat(index.byReceiver(uri, ast, "ТЗ")).hasSize(1);
assertThat(index.byReceiver(uri, ast, "СТР")).as("без учёта регистра").hasSize(1);
Expand Down
Loading