blob: e0e775700f9af60c38cb986806d849f63102112c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Andrey Loskutov.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Andrey Loskutov <loskutov@gmx.de> - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.editors.tests;
import static org.eclipse.ui.editors.tests.FileDocumentProviderTest.closeIntro;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.internal.localstore.FileSystemResourceManager;
import org.eclipse.core.internal.resources.File;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.filebuffers.tests.ResourceHelper;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
/**
* Test checking UI deadlock on modifying the TextFileDocumentProvider's
* underlined file.
*/
public class TextFileDocumentProviderTest {
private File file;
private AtomicBoolean stoppedByTest;
private AtomicBoolean stopLockingFlag;
private LockJob lockJob;
private LockJob2 lockJob2;
private TextFileDocumentProvider fileProvider;
private FileSystemResourceManager fsManager;
private IEditorPart editor;
private IWorkbenchPage page;
@Before
public void setUp() throws Exception {
closeIntro(PlatformUI.getWorkbench());
IFolder folder = ResourceHelper.createFolder("FileDocumentProviderTestProject/test");
file = (File) ResourceHelper.createFile(folder, "file.txt", "");
assertTrue(file.exists());
fsManager = file.getLocalManager();
assertTrue(fsManager.fastIsSynchronized(file));
stopLockingFlag = new AtomicBoolean(false);
stoppedByTest = new AtomicBoolean(false);
fileProvider = new TextFileDocumentProvider();
lockJob = new LockJob("Locking workspace", file, stopLockingFlag, stoppedByTest);
lockJob2 = new LockJob2("Locking workspace", file, stopLockingFlag, stoppedByTest);
// We need the editor only to get the default editor status line manager
IWorkbench workbench = PlatformUI.getWorkbench();
page = workbench.getActiveWorkbenchWindow().getActivePage();
editor = IDE.openEditor(page, file);
TestUtil.runEventLoop();
IStatusLineManager statusLineManager = editor.getEditorSite().getActionBars().getStatusLineManager();
// This is default monitor which almost all editors are using
IProgressMonitor progressMonitor = statusLineManager.getProgressMonitor();
assertNotNull(progressMonitor);
assertFalse(progressMonitor instanceof NullProgressMonitor);
assertFalse(progressMonitor instanceof EventLoopProgressMonitor);
assertTrue(progressMonitor instanceof IProgressMonitorWithBlocking);
// Because this monitor is not EventLoopProgressMonitor, it will not
// process UI events while waiting on workspace lock
fileProvider.setProgressMonitor(progressMonitor);
TestUtil.waitForJobs(500, 5000);
Job[] jobs = Job.getJobManager().find(null);
String jobsList = Arrays.toString(jobs);
System.out.println("Still running jobs: " + jobsList);
if (!Job.getJobManager().isIdle()) {
jobs = Job.getJobManager().find(null);
for (Job job : jobs) {
System.out.println("Going to cancel: " + job.getName() + " / " + job);
job.cancel();
}
}
}
@After
public void tearDown() throws Exception {
stopLockingFlag.set(true);
lockJob.cancel();
lockJob2.cancel();
if (editor != null) {
page.closeEditor(editor, false);
}
ResourceHelper.deleteProject(file.getProject().getName());
TestUtil.runEventLoop();
TestUtil.cleanUp();
}
@Test
public void testSynchronizeInputWhileWorkspaceIsLocked1() throws Exception {
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=482354
assertNotNull("Test must run in UI thread", Display.getCurrent());
fileProvider.connect(editor.getEditorInput());
// Start workspace job which will lock workspace operations on file via
// rule
lockJob.schedule();
// touch the file of the editor
makeSureResourceIsOutOfDate();
// Put an UI event in the queue which will stop the workspace lock job
// after a delay so that we can verify the UI events are still
// dispatched after the call to refreshFile() below
Display.getCurrent().timerExec(500, () -> {
stopLockingFlag.set(true);
System.out.println("UI event dispatched, lock removed");
});
// Original code will lock UI thread here because it will try to acquire
// resource lock and no one will process UI events anymore
fileProvider.synchronize(editor.getEditorInput());
System.out.println("Busy wait terminated, UI thread is operable again!");
assertFalse("Test deadlocked while waiting on resource lock", stoppedByTest.get());
assertTrue(stopLockingFlag.get());
}
@Test
public void testSynchronizeInputWhileWorkspaceIsLocked2() throws Exception {
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=482354
assertNotNull("Test must run in UI thread", Display.getCurrent());
fileProvider.connect(editor.getEditorInput());
// Start workspace job which will lock workspace operations on file via
// rule
lockJob.schedule();
// touch the file of the editor
makeSureResourceIsOutOfDate();
// Put an UI event in the queue which will stop the workspace lock job
// after a delay so that we can verify the UI events are still
// dispatched after the call to refreshFile() below
Display.getCurrent().timerExec(500, () -> {
stopLockingFlag.set(true);
System.out.println("UI event dispatched, lock removed");
});
// Original code will lock UI thread here because it will try to acquire
// resource lock and no one will process UI events anymore
fileProvider.synchronize(editor.getEditorInput());
System.out.println("Busy wait terminated, UI thread is operable again!");
assertFalse("Test deadlocked while waiting on resource lock", stoppedByTest.get());
assertTrue(stopLockingFlag.get());
}
@Test
public void testValidateStateForFileWhileWorkspaceIsLocked() throws Exception {
assertNotNull("Test must run in UI thread", Display.getCurrent());
fileProvider.connect(editor.getEditorInput());
// Start workspace job which will lock workspace operations on file
lockJob2.schedule();
Thread.sleep(100);
// Put an UI event in the queue which will stop the workspace lock job
// after a delay
Display.getCurrent().timerExec(600, () -> {
stopLockingFlag.set(true);
System.out.println("UI event dispatched, lock removed");
});
// Original code will lock UI thread here because it will try to acquire
// workspace lock and no one will process UI events anymore
fileProvider.validateState(editor.getEditorInput(), editor.getSite().getShell());
System.out.println("Busy wait terminated, UI thread is operable again!");
assertFalse("Test deadlocked while waiting on resource lock", stoppedByTest.get());
assertTrue(stopLockingFlag.get());
}
/*
* Set current time stamp via java.nio to make sure
* org.eclipse.core.internal.resources.File.refreshLocal(int,
* IProgressMonitor) will call super.refreshLocal(IResource.DEPTH_ZERO,
* monitor) and so lock the UI by trying to access resource locked by the
* job
*/
private void makeSureResourceIsOutOfDate() throws Exception {
int count = 0;
Files.setLastModifiedTime(file.getLocation().toFile().toPath(),
FileTime.fromMillis(System.currentTimeMillis()));
// Give the file system a chance to have a *different* timestamp
Thread.sleep(100);
while (fsManager.fastIsSynchronized(file) && count < 1000) {
Files.setLastModifiedTime(file.getLocation().toFile().toPath(),
FileTime.fromMillis(System.currentTimeMillis()));
Thread.sleep(10);
count++;
}
System.out.println("Managed to update file after " + count + " attempts");
assertFalse(fsManager.fastIsSynchronized(file));
}
}