天天看點

Eclipse插件開發(RCP)資源不同步問題分析

問題

資源不同步的問題對于插件開發的哥們應該都不是很陌生,記得剛到兩年前剛接觸插件開發的時候,由于産品中代碼中很多都是用

java.io.File

操作檔案資源,導緻經常有這種問題發生,例如删除不掉、内容更新失敗等。下班之前,以資源删除失敗為例子,寫篇小随筆,把這個資源不同步的問題多少說道說道。

首先看個可能不陌生的錯誤(錯誤本質上都是不同步引起,但是可能包裝形式很多):

Eclipse插件開發(RCP)資源不同步問題分析

下面是引起錯誤的代碼:

public void run(IAction action) {
          try {
              //擷取一個存在的檔案
              IFile eclipseFile = ResourcesPlugin.getWorkspace().getRoot().getProject("project").getFile(new Path("folder/file.txt"));
              
              //用java IO更新底層資源
              eclipseFile.getLocation().toFile().setLastModified(eclipseFile.getLocalTimeStamp() + 100);
              
              //删除檔案
             eclipseFile.delete(false, null);
         } catch (CoreException e) {
             Activator.getDefault().getLog().log(new Status(IStatus.ERROR, "aaa", 99, "删除資源失敗", e));
         }
     }
           

猜測

是不是我們用Java IO修改了檔案,而Eclipse工作區不知道,這可能就是不同步?

分析

一個檔案資源的狀态描述,我們可以從兩個層面來看:一個Eclipse工作區層面的資源狀态;二是檔案系統層面的資源狀态。對于這兩者(

ResourceInfo

IFileInfo

),Eclipse資源管理子產品中都有對應的類型支援。

ResourceInfo

ResourceInfo

:封裝了工作區對一個檔案資源的描述,也就是我們常說的工作區資源樹上的一個資料節點,Eclipse資源管理中的

IResource

系列接口本身也是

ResourceInfo

的代理,

ResourceInfo

主要操作如下:

Eclipse插件開發(RCP)資源不同步問題分析
Eclipse插件開發(RCP)資源不同步問題分析

ResourceInfo

的主要正常擷取方式

Workspace.getResourceInfo

:

public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) {
         try {
             if (path.segmentCount() == 0) {
                 ResourceInfo info = (ResourceInfo) tree.getTreeData();
                 Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$
                 return info;
             }
             ResourceInfo result = null;
             if (!tree.includes(path))
                 return null;
             if (mutable)
                 result = (ResourceInfo) tree.openElementData(path);
             else
                 result = (ResourceInfo) tree.getElementData(path);
             if (result != null && (!phantom && result.isSet(M_PHANTOM)))
                 return null;
             return result;
         } catch (IllegalArgumentException e) {
             return null;
         }
     }
           

資源樹的影子出來了,我們擷取

ResourceInfo

的過程其實就是在資源樹上面定位對應資料節點的過程。那可以自然的猜測(有興趣的Tx可以接着撒兩眼資源樹是如何實作的),

ResourceInfo

的擷取過程并不是每次都會産生一個新的

ResourceInfo

執行個體,因為直覺告訴我們這可能是性能敏感的。

IFileInfo

IFileInfo

:一個資源在特定時間點上的狀态快照(

snapshot

),可以了解為一個底層檔案系統資源對應的靜态隻讀資訊的集合。我們看一下它的擷取方式,

IFileStore.fetchInfo()

實作(有關

IFleStore

這裡就省略了)

public IFileInfo fetchInfo(int options, IProgressMonitor monitor) {
    if (LocalFileNatives.usingNatives()) {
         FileInfo info = LocalFileNatives.fetchFileInfo(filePath);
         //natives don't set the file name on all platforms
         if (info.getName().length() == 0)
             info.setName(file.getName());
         return info;
     }
     //in-lined non-native implementation
     FileInfo info = new FileInfo(file.getName());
     final long lastModified = file.lastModified();
     if (lastModified <= 0) {
         //if the file doesn't exist, all other attributes should be default values
         info.setExists(false);
         return info;
     }
     info.setLastModified(lastModified);
     info.setExists(true);
     info.setLength(file.length());
     info.setDirectory(file.isDirectory());
     info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, file.exists() && !file.canWrite());
     info.setAttribute(EFS.ATTRIBUTE_HIDDEN, file.isHidden());
     return info;
 }
           

可以看出來,每次擷取IFileInfo的過程是每次都産生新的執行個體,這個新的執行個體來描述該時間點上的檔案狀态。

Eclipse插件開發(RCP)資源不同步問題分析

到這裡我們知道了,

ResourceInfo

其實是記憶體中Eclipse維護的一個東東,

IFileInfo

是實時擷取的,那麼在特定的時間點上面,前者有可能和後者不統一。 這是不是就是不同步問題的根源呢?

資源不同步檢查

為了驗證上面的猜測,我們追蹤調試一下文章開頭提到的測試代碼:

Eclipse插件開發(RCP)資源不同步問題分析

找到了檢查檔案資源是否同步的代碼,

FileSystemResourceManager.isSynchronized(IResource target, int depth)

,資源樹在檢查一個資源是否同步時候(

IResourceTree.isisSynchronized(IResource target, int depth)

),也是委托給了

FileSystemResourceManager

public boolean isSynchronized(IResource target, int depth) {
    switch (target.getType()) {
        case IResource.ROOT :
            if (depth == IResource.DEPTH_ZERO)
                return true;
            //check sync on child projects.
            depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
            IProject[] projects = ((IWorkspaceRoot) target).getProjects();
            for (int i = 0; i < projects.length; i++) {
                if (!isSynchronized(projects[i], depth))
                    return false;
            }
            return true;
        case IResource.PROJECT :
            if (!target.isAccessible())
                return true;
            break;
        case IResource.FILE :
            if (fastIsSynchronized((File) target))
                return true;
            break;
    }
    IsSynchronizedVisitor visitor = new IsSynchronizedVisitor(Policy.monitorFor(null));
    UnifiedTree tree = new UnifiedTree(target);
    try {
        tree.accept(visitor, depth);
    } catch (CoreException e) {
        Policy.log(e);
        return false;
    } catch (IsSynchronizedVisitor.ResourceChangedException e) {
        //visitor throws an exception if out of sync
        return false;
    }
    return true;
}
           

我們接着看一下上面負責File級别同步檢查的

FileSystemResourceManager.fastIsSynchronized

方法:

public boolean fastIsSynchronized(File target) {
         ResourceInfo info = target.getResourceInfo(false, false);
         if (target.exists(target.getFlags(info), true)) {
             IFileInfo fileInfo = getStore(target).fetchInfo();
             if (!fileInfo.isDirectory() && info.getLocalSyncInfo() == fileInfo.getLastModified())
                 return true;
         }
         return false;
     }
           

注意:

info.getLocalSyncInfo() == fileInfo.getLastModified())

告訴我們,對于一個檔案級别的資源,判斷是否同步就是檢查Eclipse維護的時間戳和底層檔案系統的時間戳是否一緻!!!

小結

如果用Eclipse

IResource

API來修改檔案資源,Eclipse自己會知道;如果用

java IO

或者

java NIO

來修改檔案資源,Eclipse就一無所知,狀态維護就會出問題。不知道就出事情了,不同步隻是後果之一,變化跟蹤也将失效,

resource change event

也将無從産生了

反思

  1. 對于非File級别的資源,為什麼同步檢查不是很嚴格呢?

    因為在不同作業系統不同類型分區上面,一個檔案夾下面的檔案資源被修改了,檔案夾的時間戳并不保證會及時更新。這是很底層的東西了,就不接着講了。 如果你想修正這個問題,你可以對目錄時間戳做自己的維護(這是可行的,我們産品裡面就是這麼幹的)。

  2. ResourceInfo

    IFileInfo

    好像不怎麼使用?

    确實,

    Ecipse

    也不想讓開發者去直接使用它。例如:

    getResourceInfo

    是在

    Resource

    中提供的,而不是在

    IResource

    接口中定義的。
  3. 那如何同步呢?

    IResource.refreshLocal(int depth, IProgressMonitor monitor)

  • depth 同步深度,可取值有三個

    IResource.DEPTH_ZERO

    IResource.DEPTH_ONE

    IResource.DEPTH_INFINITE

    。具體含義可參考

    IResource

    中相應文檔。
  • monitor 進度條,所有實作自

    IProgressMonitor

    的進度條都可以,

    null

    表示靜默同步,即不顯示進度條。

常用示例

// 設定同步深度: 檔案及其子檔案
int depth = IResource.DEPTH_INFINITE;
// 設定進度條: 靜默同步
IProgressMonitor monitor = null;
ResourcesPlugin.getWorkspace().getRoot().refreshLocal(depth,monitor)