| /******************************************************************************* |
| * 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.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); |
| |
| // 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)); |
| } |
| |
| } |
| |
| |