/** @author max */ public class PersistentFSImpl extends PersistentFS implements ApplicationComponent { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS"); private final MessageBus myEventBus; private final ReadWriteLock myRootsLock = new ReentrantReadWriteLock(); private final Map<String, VirtualFileSystemEntry> myRoots = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY); private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>(); private final ConcurrentIntObjectMap<VirtualFileSystemEntry> myIdToDirCache = ContainerUtil.createConcurrentIntObjectMap(); private final Object myInputLock = new Object(); private final AtomicBoolean myShutDown = new AtomicBoolean(false); @SuppressWarnings("FieldCanBeLocal") private final LowMemoryWatcher myWatcher = LowMemoryWatcher.register( new Runnable() { @Override public void run() { clearIdCache(); } }); public PersistentFSImpl(@NotNull MessageBus bus) { myEventBus = bus; ShutDownTracker.getInstance() .registerShutdownTask( new Runnable() { @Override public void run() { performShutdown(); } }); } @Override public void initComponent() { FSRecords.connect(); } @Override public void disposeComponent() { performShutdown(); } private void performShutdown() { if (myShutDown.compareAndSet(false, true)) { LOG.info("VFS dispose started"); FSRecords.dispose(); LOG.info("VFS dispose completed"); } } @Override @NonNls @NotNull public String getComponentName() { return "app.component.PersistentFS"; } @Override public boolean areChildrenLoaded(@NotNull final VirtualFile dir) { return areChildrenLoaded(getFileId(dir)); } @Override public long getCreationTimestamp() { return FSRecords.getCreationTimestamp(); } @NotNull private static NewVirtualFileSystem getDelegate(@NotNull VirtualFile file) { return (NewVirtualFileSystem) file.getFileSystem(); } @Override public boolean wereChildrenAccessed(@NotNull final VirtualFile dir) { return FSRecords.wereChildrenAccessed(getFileId(dir)); } @Override @NotNull public String[] list(@NotNull final VirtualFile file) { int id = getFileId(file); FSRecords.NameId[] nameIds = FSRecords.listAll(id); if (!areChildrenLoaded(id)) { nameIds = persistAllChildren(file, id, nameIds); } return ContainerUtil.map2Array( nameIds, String.class, new Function<FSRecords.NameId, String>() { @Override public String fun(FSRecords.NameId id) { return id.name.toString(); } }); } @Override @NotNull public String[] listPersisted(@NotNull VirtualFile parent) { return listPersisted(FSRecords.list(getFileId(parent))); } @NotNull private static String[] listPersisted(@NotNull int[] childrenIds) { String[] names = ArrayUtil.newStringArray(childrenIds.length); for (int i = 0; i < childrenIds.length; i++) { names[i] = FSRecords.getName(childrenIds[i]); } return names; } @NotNull private static FSRecords.NameId[] persistAllChildren( @NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) { final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file)); String[] delegateNames = VfsUtil.filterNames(fs.list(file)); if (delegateNames.length == 0 && current.length > 0) { return current; } Set<String> toAdd = ContainerUtil.newHashSet(delegateNames); for (FSRecords.NameId nameId : current) { toAdd.remove(nameId.name.toString()); } final TIntArrayList childrenIds = new TIntArrayList(current.length + toAdd.size()); final List<FSRecords.NameId> nameIds = ContainerUtil.newArrayListWithCapacity(current.length + toAdd.size()); for (FSRecords.NameId nameId : current) { childrenIds.add(nameId.id); nameIds.add(nameId); } for (String newName : toAdd) { FakeVirtualFile child = new FakeVirtualFile(file, newName); FileAttributes attributes = fs.getAttributes(child); if (attributes != null) { int childId = createAndFillRecord(fs, child, id, attributes); childrenIds.add(childId); nameIds.add(new FSRecords.NameId(childId, FileNameCache.storeName(newName), newName)); } } FSRecords.updateList(id, childrenIds.toNativeArray()); setChildrenCached(id); return nameIds.toArray(new FSRecords.NameId[nameIds.size()]); } public static void setChildrenCached(int id) { int flags = FSRecords.getFlags(id); FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true); } @Override @NotNull public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) { final int parentId = getFileId(parent); FSRecords.NameId[] nameIds = FSRecords.listAll(parentId); if (!areChildrenLoaded(parentId)) { return persistAllChildren(parent, parentId, nameIds); } return nameIds; } private static boolean areChildrenLoaded(final int parentId) { return (FSRecords.getFlags(parentId) & CHILDREN_CACHED_FLAG) != 0; } @Override @Nullable public DataInputStream readAttribute( @NotNull final VirtualFile file, @NotNull final FileAttribute att) { return FSRecords.readAttributeWithLock(getFileId(file), att); } @Override @NotNull public DataOutputStream writeAttribute( @NotNull final VirtualFile file, @NotNull final FileAttribute att) { return FSRecords.writeAttribute(getFileId(file), att); } @Nullable private static DataInputStream readContent(@NotNull VirtualFile file) { return FSRecords.readContent(getFileId(file)); } @Nullable private static DataInputStream readContentById(int contentId) { return FSRecords.readContentById(contentId); } @NotNull private static DataOutputStream writeContent(@NotNull VirtualFile file, boolean readOnly) { return FSRecords.writeContent(getFileId(file), readOnly); } private static void writeContent( @NotNull VirtualFile file, ByteSequence content, boolean readOnly) throws IOException { FSRecords.writeContent(getFileId(file), content, readOnly); } @Override public int storeUnlinkedContent(@NotNull byte[] bytes) { return FSRecords.storeUnlinkedContent(bytes); } @Override public int getModificationCount(@NotNull final VirtualFile file) { return FSRecords.getModCount(getFileId(file)); } @Override public int getCheapFileSystemModificationCount() { return FSRecords.getLocalModCount(); } @Override public int getFilesystemModificationCount() { return FSRecords.getModCount(); } private static boolean writeAttributesToRecord( final int id, final int parentId, @NotNull VirtualFile file, @NotNull NewVirtualFileSystem fs, @NotNull FileAttributes attributes) { String name = file.getName(); if (!name.isEmpty()) { if (namesEqual(fs, name, FSRecords.getName(id))) return false; // TODO: Handle root attributes change. } else { if (areChildrenLoaded(id)) return false; // TODO: hack } FSRecords.writeAttributesToRecord(id, parentId, attributes, name); return true; } @Override public int getFileAttributes(int id) { assert id > 0; //noinspection MagicConstant return FSRecords.getFlags(id); } @Override public boolean isDirectory(@NotNull final VirtualFile file) { return isDirectory(getFileAttributes(getFileId(file))); } private static int getParent(final int id) { assert id > 0; return FSRecords.getParent(id); } private static boolean namesEqual(@NotNull VirtualFileSystem fs, @NotNull String n1, String n2) { return fs.isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2); } @Override public boolean exists(@NotNull final VirtualFile fileOrDirectory) { return ((VirtualFileWithId) fileOrDirectory).getId() > 0; } @Override public long getTimeStamp(@NotNull final VirtualFile file) { return FSRecords.getTimestamp(getFileId(file)); } @Override public void setTimeStamp(@NotNull final VirtualFile file, final long modStamp) throws IOException { final int id = getFileId(file); FSRecords.setTimestamp(id, modStamp); getDelegate(file).setTimeStamp(file, modStamp); } private static int getFileId(@NotNull VirtualFile file) { final int id = ((VirtualFileWithId) file).getId(); if (id <= 0) { throw new InvalidVirtualFileAccessException(file); } return id; } @Override public boolean isSymLink(@NotNull VirtualFile file) { return isSymLink(getFileAttributes(getFileId(file))); } @Override public String resolveSymLink(@NotNull VirtualFile file) { throw new UnsupportedOperationException(); } @Override public boolean isSpecialFile(@NotNull VirtualFile file) { return isSpecialFile(getFileAttributes(getFileId(file))); } @Override public boolean isWritable(@NotNull VirtualFile file) { return (getFileAttributes(getFileId(file)) & IS_READ_ONLY) == 0; } @Override public boolean isHidden(@NotNull VirtualFile file) { return (getFileAttributes(getFileId(file)) & IS_HIDDEN) != 0; } @Override public void setWritable(@NotNull final VirtualFile file, final boolean writableFlag) throws IOException { getDelegate(file).setWritable(file, writableFlag); boolean oldWritable = isWritable(file); if (oldWritable != writableFlag) { processEvent( new VFilePropertyChangeEvent( this, file, VirtualFile.PROP_WRITABLE, oldWritable, writableFlag, false)); } } @Override public int getId( @NotNull VirtualFile parent, @NotNull String childName, @NotNull NewVirtualFileSystem fs) { int parentId = getFileId(parent); int[] children = FSRecords.list(parentId); if (children.length > 0) { // fast path, check that some child has same nameId as given name, this avoid O(N) on // retrieving names for processing non-cached children int nameId = FSRecords.getNameId(childName); for (final int childId : children) { if (nameId == FSRecords.getNameId(childId)) { return childId; } } // for case sensitive system the above check is exhaustive in consistent state of vfs } for (final int childId : children) { if (namesEqual(fs, childName, FSRecords.getName(childId))) return childId; } final VirtualFile fake = new FakeVirtualFile(parent, childName); final FileAttributes attributes = fs.getAttributes(fake); if (attributes != null) { final int child = createAndFillRecord(fs, fake, parentId, attributes); FSRecords.updateList(parentId, ArrayUtil.append(children, child)); return child; } return 0; } @Override public long getLength(@NotNull final VirtualFile file) { long len; if (mustReloadContent(file)) { len = reloadLengthFromDelegate(file, getDelegate(file)); } else { final int id = getFileId(file); len = FSRecords.getLength(id); } return len; } @NotNull @Override public VirtualFile copyFile( Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile parent, @NotNull String name) throws IOException { getDelegate(file).copyFile(requestor, file, parent, name); processEvent(new VFileCopyEvent(requestor, file, parent, name)); final VirtualFile child = parent.findChild(name); if (child == null) { throw new IOException("Cannot create child"); } return child; } @NotNull @Override public VirtualFile createChildDirectory( Object requestor, @NotNull VirtualFile parent, @NotNull String dir) throws IOException { getDelegate(parent).createChildDirectory(requestor, parent, dir); processEvent(new VFileCreateEvent(requestor, parent, dir, true, false)); final VirtualFile child = parent.findChild(dir); if (child == null) { throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath()); } return child; } @NotNull @Override public VirtualFile createChildFile( Object requestor, @NotNull VirtualFile parent, @NotNull String file) throws IOException { getDelegate(parent).createChildFile(requestor, parent, file); processEvent(new VFileCreateEvent(requestor, parent, file, false, false)); final VirtualFile child = parent.findChild(file); if (child == null) { throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath()); } return child; } @Override public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException { final NewVirtualFileSystem delegate = getDelegate(file); delegate.deleteFile(requestor, file); if (!delegate.exists(file)) { processEvent(new VFileDeleteEvent(requestor, file, false)); } } @Override public void renameFile(final Object requestor, @NotNull VirtualFile file, @NotNull String newName) throws IOException { getDelegate(file).renameFile(requestor, file, newName); String oldName = file.getName(); if (!newName.equals(oldName)) { processEvent( new VFilePropertyChangeEvent( requestor, file, VirtualFile.PROP_NAME, oldName, newName, false)); } } @Override @NotNull public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException { return contentsToByteArray(file, true); } @Override @NotNull public byte[] contentsToByteArray(@NotNull final VirtualFile file, boolean cacheContent) throws IOException { InputStream contentStream = null; boolean reloadFromDelegate; boolean outdated; int fileId; synchronized (myInputLock) { fileId = getFileId(file); outdated = checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L; reloadFromDelegate = outdated || (contentStream = readContent(file)) == null; } if (reloadFromDelegate) { final NewVirtualFileSystem delegate = getDelegate(file); final byte[] content; if (outdated) { // in this case, file can have out-of-date length. so, update it first (it's needed for // correct contentsToByteArray() work) // see IDEA-90813 for possible bugs FSRecords.setLength(fileId, delegate.getLength(file)); content = delegate.contentsToByteArray(file); } else { // a bit of optimization content = delegate.contentsToByteArray(file); FSRecords.setLength(fileId, content.length); } ApplicationEx application = (ApplicationEx) ApplicationManager.getApplication(); // we should cache every local files content // because the local history feature is currently depends on this cache, // perforce offline mode as well if ((!delegate.isReadOnly() || // do not cache archive content unless asked cacheContent && !application.isInternal() && !application.isUnitTestMode()) && content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) { synchronized (myInputLock) { writeContent(file, new ByteSequence(content), delegate.isReadOnly()); setFlag(file, MUST_RELOAD_CONTENT, false); } } return content; } else { try { final int length = (int) file.getLength(); assert length >= 0 : file; return FileUtil.loadBytes(contentStream, length); } catch (IOException e) { throw FSRecords.handleError(e); } } } @Override @NotNull public byte[] contentsToByteArray(int contentId) throws IOException { final DataInputStream stream = readContentById(contentId); assert stream != null : contentId; return FileUtil.loadBytes(stream); } @Override @NotNull public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException { synchronized (myInputLock) { InputStream contentStream; if (mustReloadContent(file) || (contentStream = readContent(file)) == null) { NewVirtualFileSystem delegate = getDelegate(file); long len = reloadLengthFromDelegate(file, delegate); InputStream nativeStream = delegate.getInputStream(file); if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream; return createReplicator(file, nativeStream, len, delegate.isReadOnly()); } else { return contentStream; } } } private static long reloadLengthFromDelegate( @NotNull VirtualFile file, @NotNull NewVirtualFileSystem delegate) { final long len = delegate.getLength(file); FSRecords.setLength(getFileId(file), len); return len; } private InputStream createReplicator( @NotNull final VirtualFile file, final InputStream nativeStream, final long fileLength, final boolean readOnly) throws IOException { if (nativeStream instanceof BufferExposingByteArrayInputStream) { // optimization BufferExposingByteArrayInputStream byteStream = (BufferExposingByteArrayInputStream) nativeStream; byte[] bytes = byteStream.getInternalBuffer(); storeContentToStorage(fileLength, file, readOnly, bytes, bytes.length); return nativeStream; } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") final BufferExposingByteArrayOutputStream cache = new BufferExposingByteArrayOutputStream((int) fileLength); return new ReplicatorInputStream(nativeStream, cache) { @Override public void close() throws IOException { super.close(); storeContentToStorage(fileLength, file, readOnly, cache.getInternalBuffer(), cache.size()); } }; } private void storeContentToStorage( long fileLength, @NotNull VirtualFile file, boolean readOnly, @NotNull byte[] bytes, int bytesLength) throws IOException { synchronized (myInputLock) { if (bytesLength == fileLength) { writeContent(file, new ByteSequence(bytes, 0, bytesLength), readOnly); setFlag(file, MUST_RELOAD_CONTENT, false); } else { setFlag(file, MUST_RELOAD_CONTENT, true); } } } private static boolean mustReloadContent(@NotNull VirtualFile file) { int fileId = getFileId(file); return checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L; } @Override @NotNull public OutputStream getOutputStream( @NotNull final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp) throws IOException { return new ByteArrayOutputStream() { private boolean closed; // protection against user calling .close() twice @Override public void close() throws IOException { if (closed) return; super.close(); VFileContentChangeEvent event = new VFileContentChangeEvent( requestor, file, file.getModificationStamp(), modStamp, false); List<VFileContentChangeEvent> events = Collections.singletonList(event); BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES); publisher.before(events); NewVirtualFileSystem delegate = getDelegate(file); OutputStream ioFileStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp); // FSRecords.ContentOutputStream already buffered, no need to wrap in BufferedStream OutputStream persistenceStream = writeContent(file, delegate.isReadOnly()); try { persistenceStream.write(buf, 0, count); } finally { try { ioFileStream.write(buf, 0, count); } finally { closed = true; persistenceStream.close(); ioFileStream.close(); executeTouch(file, false, event.getModificationStamp()); publisher.after(events); } } } }; } @Override public int acquireContent(@NotNull VirtualFile file) { return FSRecords.acquireFileContent(getFileId(file)); } @Override public void releaseContent(int contentId) { FSRecords.releaseContent(contentId); } @Override public int getCurrentContentId(@NotNull VirtualFile file) { return FSRecords.getContentId(getFileId(file)); } @Override public void moveFile( final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent) throws IOException { getDelegate(file).moveFile(requestor, file, newParent); processEvent(new VFileMoveEvent(requestor, file, newParent)); } @RequiredWriteAction private void processEvent(@NotNull VFileEvent event) { processEvents(Collections.singletonList(event)); } private static class EventWrapper { private final VFileDeleteEvent event; private final int id; private EventWrapper(final VFileDeleteEvent event, final int id) { this.event = event; this.id = id; } } @NotNull private static final Comparator<EventWrapper> DEPTH_COMPARATOR = new Comparator<EventWrapper>() { @Override public int compare(@NotNull final EventWrapper o1, @NotNull final EventWrapper o2) { return o1.event.getFileDepth() - o2.event.getFileDepth(); } }; @NotNull private static List<VFileEvent> validateEvents(@NotNull List<VFileEvent> events) { final List<EventWrapper> deletionEvents = ContainerUtil.newArrayList(); for (int i = 0, size = events.size(); i < size; i++) { final VFileEvent event = events.get(i); if (event instanceof VFileDeleteEvent && event.isValid()) { deletionEvents.add(new EventWrapper((VFileDeleteEvent) event, i)); } } final TIntHashSet invalidIDs; if (deletionEvents.isEmpty()) { invalidIDs = EmptyIntHashSet.INSTANCE; } else { ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR); invalidIDs = new TIntHashSet(deletionEvents.size()); final Set<VirtualFile> dirsToBeDeleted = new THashSet<VirtualFile>(deletionEvents.size()); nextEvent: for (EventWrapper wrapper : deletionEvents) { final VirtualFile candidate = wrapper.event.getFile(); VirtualFile parent = candidate; while (parent != null) { if (dirsToBeDeleted.contains(parent)) { invalidIDs.add(wrapper.id); continue nextEvent; } parent = parent.getParent(); } if (candidate.isDirectory()) { dirsToBeDeleted.add(candidate); } } } final List<VFileEvent> filtered = new ArrayList<VFileEvent>(events.size() - invalidIDs.size()); for (int i = 0, size = events.size(); i < size; i++) { final VFileEvent event = events.get(i); if (event.isValid() && !(event instanceof VFileDeleteEvent && invalidIDs.contains(i))) { filtered.add(event); } } return filtered; } @RequiredWriteAction @Override public void processEvents(@NotNull List<VFileEvent> events) { ApplicationManager.getApplication().assertWriteAccessAllowed(); List<VFileEvent> validated = validateEvents(events); BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES); publisher.before(validated); THashMap<VirtualFile, List<VFileEvent>> parentToChildrenEventsChanges = null; for (VFileEvent event : validated) { VirtualFile changedParent = null; if (event instanceof VFileCreateEvent) { changedParent = ((VFileCreateEvent) event).getParent(); ((VFileCreateEvent) event).resetCache(); } else if (event instanceof VFileDeleteEvent) { changedParent = ((VFileDeleteEvent) event).getFile().getParent(); } if (changedParent != null) { if (parentToChildrenEventsChanges == null) parentToChildrenEventsChanges = new THashMap<VirtualFile, List<VFileEvent>>(); List<VFileEvent> parentChildrenChanges = parentToChildrenEventsChanges.get(changedParent); if (parentChildrenChanges == null) { parentToChildrenEventsChanges.put( changedParent, parentChildrenChanges = new SmartList<VFileEvent>()); } parentChildrenChanges.add(event); } else { applyEvent(event); } } if (parentToChildrenEventsChanges != null) { parentToChildrenEventsChanges.forEachEntry( new TObjectObjectProcedure<VirtualFile, List<VFileEvent>>() { @Override public boolean execute(VirtualFile parent, List<VFileEvent> childrenEvents) { applyChildrenChangeEvents(parent, childrenEvents); return true; } }); parentToChildrenEventsChanges.clear(); } publisher.after(validated); } private void applyChildrenChangeEvents(VirtualFile parent, List<VFileEvent> events) { final NewVirtualFileSystem delegate = getDelegate(parent); TIntArrayList childrenIdsUpdated = new TIntArrayList(); List<VirtualFile> childrenToBeUpdated = new SmartList<VirtualFile>(); final int parentId = getFileId(parent); assert parentId != 0; TIntHashSet parentChildrenIds = new TIntHashSet(FSRecords.list(parentId)); boolean hasRemovedChildren = false; for (VFileEvent event : events) { if (event instanceof VFileCreateEvent) { String name = ((VFileCreateEvent) event).getChildName(); final VirtualFile fake = new FakeVirtualFile(parent, name); final FileAttributes attributes = delegate.getAttributes(fake); if (attributes != null) { final int childId = createAndFillRecord(delegate, fake, parentId, attributes); assert parent instanceof VirtualDirectoryImpl : parent; final VirtualDirectoryImpl dir = (VirtualDirectoryImpl) parent; VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem()); childrenToBeUpdated.add(child); childrenIdsUpdated.add(childId); parentChildrenIds.add(childId); } } else if (event instanceof VFileDeleteEvent) { VirtualFile file = ((VFileDeleteEvent) event).getFile(); if (!file.exists()) { LOG.error("Deleting a file, which does not exist: " + file.getPath()); continue; } hasRemovedChildren = true; int id = getFileId(file); childrenToBeUpdated.add(file); childrenIdsUpdated.add(-id); parentChildrenIds.remove(id); } } FSRecords.updateList(parentId, parentChildrenIds.toArray()); if (hasRemovedChildren) clearIdCache(); VirtualDirectoryImpl parentImpl = (VirtualDirectoryImpl) parent; for (int i = 0, len = childrenIdsUpdated.size(); i < len; ++i) { final int childId = childrenIdsUpdated.get(i); final VirtualFile childFile = childrenToBeUpdated.get(i); if (childId > 0) { parentImpl.addChild((VirtualFileSystemEntry) childFile); } else { FSRecords.deleteRecordRecursively(-childId); parentImpl.removeChild(childFile); invalidateSubtree(childFile); } } } @Override @Nullable public VirtualFileSystemEntry findRoot( @NotNull String basePath, @NotNull NewVirtualFileSystem fs) { if (basePath.isEmpty()) { LOG.error("Invalid root, fs=" + fs); return null; } String rootUrl = normalizeRootUrl(basePath, fs); myRootsLock.readLock().lock(); try { VirtualFileSystemEntry root = myRoots.get(rootUrl); if (root != null) return root; } finally { myRootsLock.readLock().unlock(); } final VirtualFileSystemEntry newRoot; int rootId = FSRecords.findRootRecord(rootUrl); VfsData.Segment segment = VfsData.getSegment(rootId, true); VfsData.DirectoryData directoryData = new VfsData.DirectoryData(); if (fs instanceof ArchiveFileSystem) { String parentPath = basePath.substring(0, basePath.indexOf(ArchiveFileSystem.ARCHIVE_SEPARATOR)); VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(parentPath); if (parentFile == null) return null; FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(parentFile.getName()); if (!(type instanceof ArchiveFileType)) return null; newRoot = new ArchiveRoot(fs, rootId, segment, directoryData, parentFile); } else { newRoot = new FsRoot(fs, rootId, segment, directoryData, basePath); } FileAttributes attributes = fs.getAttributes( new StubVirtualFile() { @NotNull @Override public String getPath() { return newRoot.getPath(); } @Nullable @Override public VirtualFile getParent() { return null; } }); if (attributes == null || !attributes.isDirectory()) { return null; } boolean mark = false; myRootsLock.writeLock().lock(); try { VirtualFileSystemEntry root = myRoots.get(rootUrl); if (root != null) return root; VfsData.initFile(rootId, segment, -1, directoryData); mark = writeAttributesToRecord(rootId, 0, newRoot, fs, attributes); myRoots.put(rootUrl, newRoot); myRootsById.put(rootId, newRoot); } finally { myRootsLock.writeLock().unlock(); } if (!mark && attributes.lastModified != FSRecords.getTimestamp(rootId)) { newRoot.markDirtyRecursively(); } LOG.assertTrue( rootId == newRoot.getId(), "root=" + newRoot + " expected=" + rootId + " actual=" + newRoot.getId()); return newRoot; } @NotNull private static String normalizeRootUrl( @NotNull String basePath, @NotNull NewVirtualFileSystem fs) { // need to protect against relative path of the form "/x/../y" return UriUtil.trimTrailingSlashes( fs.getProtocol() + URLUtil.SCHEME_SEPARATOR + VfsImplUtil.normalize(fs, FileUtil.toCanonicalPath(basePath))); } @Override public void clearIdCache() { myIdToDirCache.clear(); } private static final int DEPTH_LIMIT = 75; @Override @Nullable public NewVirtualFile findFileById(final int id) { return findFileById(id, false, null, 0); } @Override public NewVirtualFile findFileByIdIfCached(final int id) { return findFileById(id, true, null, 0); } @Nullable private VirtualFileSystemEntry findFileById( int id, boolean cachedOnly, TIntArrayList visited, int mask) { VirtualFileSystemEntry cached = myIdToDirCache.get(id); if (cached != null) return cached; if (visited != null && (visited.size() >= DEPTH_LIMIT || (mask & id) == id && visited.contains(id))) { @NonNls String sb = "Dead loop detected in persistent FS (id=" + id + " cached-only=" + cachedOnly + "):"; for (int i = 0; i < visited.size(); i++) { int _id = visited.get(i); sb += "\n " + _id + " '" + getName(_id) + "' " + String.format("%02x", getFileAttributes(_id)) + ' ' + myIdToDirCache.containsKey(_id); } LOG.error(sb); return null; } int parentId = getParent(id); if (parentId >= id) { if (visited == null) visited = new TIntArrayList(DEPTH_LIMIT); } if (visited != null) visited.add(id); VirtualFileSystemEntry result; if (parentId == 0) { myRootsLock.readLock().lock(); try { result = myRootsById.get(id); } finally { myRootsLock.readLock().unlock(); } } else { VirtualFileSystemEntry parentFile = findFileById(parentId, cachedOnly, visited, mask | id); if (parentFile instanceof VirtualDirectoryImpl) { result = ((VirtualDirectoryImpl) parentFile).findChildById(id, cachedOnly); } else { result = null; } } if (result != null && result.isDirectory()) { VirtualFileSystemEntry old = myIdToDirCache.put(id, result); if (old != null) result = old; } return result; } @Override @NotNull public VirtualFile[] getRoots() { myRootsLock.readLock().lock(); try { Collection<VirtualFileSystemEntry> roots = myRoots.values(); return VfsUtilCore.toVirtualFileArray(roots); } finally { myRootsLock.readLock().unlock(); } } @Override @NotNull public VirtualFile[] getRoots(@NotNull final NewVirtualFileSystem fs) { final List<VirtualFile> roots = new ArrayList<VirtualFile>(); myRootsLock.readLock().lock(); try { for (NewVirtualFile root : myRoots.values()) { if (root.getFileSystem() == fs) { roots.add(root); } } } finally { myRootsLock.readLock().unlock(); } return VfsUtilCore.toVirtualFileArray(roots); } @Override @NotNull public VirtualFile[] getLocalRoots() { List<VirtualFile> roots = ContainerUtil.newSmartList(); myRootsLock.readLock().lock(); try { for (NewVirtualFile root : myRoots.values()) { if (root.isInLocalFileSystem() && !(root.getFileSystem() instanceof TempFileSystem)) { roots.add(root); } } } finally { myRootsLock.readLock().unlock(); } return VfsUtilCore.toVirtualFileArray(roots); } private VirtualFileSystemEntry applyEvent(@NotNull VFileEvent event) { try { if (event instanceof VFileCreateEvent) { final VFileCreateEvent createEvent = (VFileCreateEvent) event; return executeCreateChild(createEvent.getParent(), createEvent.getChildName()); } else if (event instanceof VFileDeleteEvent) { final VFileDeleteEvent deleteEvent = (VFileDeleteEvent) event; executeDelete(deleteEvent.getFile()); } else if (event instanceof VFileContentChangeEvent) { final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent) event; executeTouch( contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp()); } else if (event instanceof VFileCopyEvent) { final VFileCopyEvent copyEvent = (VFileCopyEvent) event; return executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName()); } else if (event instanceof VFileMoveEvent) { final VFileMoveEvent moveEvent = (VFileMoveEvent) event; executeMove(moveEvent.getFile(), moveEvent.getNewParent()); } else if (event instanceof VFilePropertyChangeEvent) { final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent) event; if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) { executeRename(propertyChangeEvent.getFile(), (String) propertyChangeEvent.getNewValue()); } else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) { executeSetWritable( propertyChangeEvent.getFile(), ((Boolean) propertyChangeEvent.getNewValue()).booleanValue()); } else if (VirtualFile.PROP_HIDDEN.equals(propertyChangeEvent.getPropertyName())) { executeSetHidden( propertyChangeEvent.getFile(), ((Boolean) propertyChangeEvent.getNewValue()).booleanValue()); } else if (VirtualFile.PROP_SYMLINK_TARGET.equals(propertyChangeEvent.getPropertyName())) { executeSetTarget( propertyChangeEvent.getFile(), (String) propertyChangeEvent.getNewValue()); } } } catch (Exception e) { // Exception applying single event should not prevent other events from applying. LOG.error(e); } return null; } @NotNull @NonNls public String toString() { return "PersistentFS"; } private static VirtualFileSystemEntry executeCreateChild( @NotNull VirtualFile parent, @NotNull String name) { final NewVirtualFileSystem delegate = getDelegate(parent); final VirtualFile fake = new FakeVirtualFile(parent, name); final FileAttributes attributes = delegate.getAttributes(fake); if (attributes != null) { final int parentId = getFileId(parent); final int childId = createAndFillRecord(delegate, fake, parentId, attributes); appendIdToParentList(parentId, childId); assert parent instanceof VirtualDirectoryImpl : parent; final VirtualDirectoryImpl dir = (VirtualDirectoryImpl) parent; VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem()); dir.addChild(child); return child; } return null; } private static int createAndFillRecord( @NotNull NewVirtualFileSystem delegateSystem, @NotNull VirtualFile delegateFile, int parentId, @NotNull FileAttributes attributes) { final int childId = FSRecords.createRecord(); writeAttributesToRecord(childId, parentId, delegateFile, delegateSystem, attributes); return childId; } private static void appendIdToParentList(final int parentId, final int childId) { int[] childrenList = FSRecords.list(parentId); childrenList = ArrayUtil.append(childrenList, childId); FSRecords.updateList(parentId, childrenList); } private void executeDelete(@NotNull VirtualFile file) { if (!file.exists()) { LOG.error("Deleting a file, which does not exist: " + file.getPath()); return; } clearIdCache(); int id = getFileId(file); final VirtualFile parent = file.getParent(); final int parentId = parent == null ? 0 : getFileId(parent); if (parentId == 0) { String rootUrl = normalizeRootUrl(file.getPath(), (NewVirtualFileSystem) file.getFileSystem()); myRootsLock.writeLock().lock(); try { myRoots.remove(rootUrl); myRootsById.remove(id); FSRecords.deleteRootRecord(id); } finally { myRootsLock.writeLock().unlock(); } } else { removeIdFromParentList(parentId, id, parent, file); VirtualDirectoryImpl directory = (VirtualDirectoryImpl) file.getParent(); assert directory != null : file; directory.removeChild(file); } FSRecords.deleteRecordRecursively(id); invalidateSubtree(file); } private static void invalidateSubtree(@NotNull VirtualFile file) { final VirtualFileSystemEntry impl = (VirtualFileSystemEntry) file; impl.invalidate(); for (VirtualFile child : impl.getCachedChildren()) { invalidateSubtree(child); } } private static void removeIdFromParentList( final int parentId, final int id, @NotNull VirtualFile parent, VirtualFile file) { int[] childList = FSRecords.list(parentId); int index = ArrayUtil.indexOf(childList, id); if (index == -1) { throw new RuntimeException( "Cannot find child (" + id + ")" + file + "\n\tin (" + parentId + ")" + parent + "\n\tactual children:" + Arrays.toString(childList)); } childList = ArrayUtil.remove(childList, index); FSRecords.updateList(parentId, childList); } private static void executeRename(@NotNull VirtualFile file, @NotNull final String newName) { final int id = getFileId(file); FSRecords.setName(id, newName); ((VirtualFileSystemEntry) file).setNewName(newName); } private static void executeSetWritable(@NotNull VirtualFile file, boolean writableFlag) { setFlag(file, IS_READ_ONLY, !writableFlag); ((VirtualFileSystemEntry) file).updateProperty(VirtualFile.PROP_WRITABLE, writableFlag); } private static void executeSetHidden(@NotNull VirtualFile file, boolean hiddenFlag) { setFlag(file, IS_HIDDEN, hiddenFlag); ((VirtualFileSystemEntry) file).updateProperty(VirtualFile.PROP_HIDDEN, hiddenFlag); } private static void executeSetTarget(@NotNull VirtualFile file, String target) { ((VirtualFileSystemEntry) file).setLinkTarget(target); } private static void setFlag(@NotNull VirtualFile file, int mask, boolean value) { setFlag(getFileId(file), mask, value); } private static void setFlag(final int id, final int mask, final boolean value) { int oldFlags = FSRecords.getFlags(id); int flags = value ? oldFlags | mask : oldFlags & ~mask; if (oldFlags != flags) { FSRecords.setFlags(id, flags, true); } } private static boolean checkFlag(int fileId, int mask) { return (FSRecords.getFlags(fileId) & mask) != 0; } private static void executeTouch( @NotNull VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) { if (reloadContentFromDelegate) { setFlag(file, MUST_RELOAD_CONTENT, true); } final NewVirtualFileSystem delegate = getDelegate(file); final FileAttributes attributes = delegate.getAttributes(file); FSRecords.setLength(getFileId(file), attributes != null ? attributes.length : DEFAULT_LENGTH); FSRecords.setTimestamp( getFileId(file), attributes != null ? attributes.lastModified : DEFAULT_TIMESTAMP); ((VirtualFileSystemEntry) file).setModificationStamp(newModificationStamp); } private void executeMove(@NotNull VirtualFile file, @NotNull VirtualFile newParent) { clearIdCache(); final int fileId = getFileId(file); final int newParentId = getFileId(newParent); final int oldParentId = getFileId(file.getParent()); removeIdFromParentList(oldParentId, fileId, file.getParent(), file); FSRecords.setParent(fileId, newParentId); appendIdToParentList(newParentId, fileId); ((VirtualFileSystemEntry) file).setParent(newParent); } @Override public String getName(int id) { assert id > 0; return FSRecords.getName(id); } @TestOnly public void cleanPersistedContents() { final int[] roots = FSRecords.listRoots(); for (int root : roots) { cleanPersistedContentsRecursively(root); } } @TestOnly private void cleanPersistedContentsRecursively(int id) { if (isDirectory(getFileAttributes(id))) { for (int child : FSRecords.list(id)) { cleanPersistedContentsRecursively(child); } } else { setFlag(id, MUST_RELOAD_CONTENT, true); } } private abstract static class AbstractRoot extends VirtualDirectoryImpl { public AbstractRoot( int id, VfsData.Segment segment, VfsData.DirectoryData data, NewVirtualFileSystem fs) { super(id, segment, data, null, fs); } @NotNull @Override public abstract CharSequence getNameSequence(); @Override protected abstract char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef); @Override public void setNewName(@NotNull String newName) { throw new IncorrectOperationException(); } @Override public final void setParent(@NotNull VirtualFile newParent) { throw new IncorrectOperationException(); } } private static class ArchiveRoot extends AbstractRoot { private final VirtualFile myParentLocalFile; private final String myParentPath; private ArchiveRoot( @NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, VirtualFile parentLocalFile) { super(id, segment, data, fs); myParentLocalFile = parentLocalFile; myParentPath = myParentLocalFile.getPath(); } @NotNull @Override public CharSequence getNameSequence() { return myParentLocalFile.getName(); } @Override protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) { char[] chars = new char [myParentPath.length() + ArchiveFileSystem.ARCHIVE_SEPARATOR.length() + accumulatedPathLength]; positionRef[0] = copyString(chars, positionRef[0], myParentPath); positionRef[0] = copyString(chars, positionRef[0], ArchiveFileSystem.ARCHIVE_SEPARATOR); return chars; } } private static class FsRoot extends AbstractRoot { private final String myName; private FsRoot( @NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, @NotNull String basePath) { super(id, segment, data, fs); myName = FileUtil.toSystemIndependentName(basePath); } @NotNull @Override public CharSequence getNameSequence() { return myName; } @Override protected char[] appendPathOnFileSystem(int pathLength, int[] position) { String name = getName(); int nameLength = name.length(); int rootPathLength = pathLength + nameLength; // otherwise we called this as a part of longer file path calculation and slash will be added // anyway boolean appendSlash = SystemInfo.isWindows && nameLength == 2 && name.charAt(1) == ':' && pathLength == 0; if (appendSlash) ++rootPathLength; char[] chars = new char[rootPathLength]; position[0] = copyString(chars, position[0], name); if (appendSlash) { chars[position[0]++] = '/'; } return chars; } } }
private void processBatch( @NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) { assert !myApplication.isDispatchThread(); final int resolvedInPreviousBatch = this.resolvedInPreviousBatch; final int totalSize = files.size() + resolvedInPreviousBatch; final ConcurrentIntObjectMap<int[]> fileToForwardIds = ContainerUtil.createConcurrentIntObjectMap(); final Set<VirtualFile> toProcess = Collections.synchronizedSet(files); indicator.setIndeterminate(false); ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable) indicator); long start = System.currentTimeMillis(); Processor<VirtualFile> processor = file -> { double fraction = 1 - toProcess.size() * 1.0 / totalSize; indicator.setFraction(fraction); try { if (!file.isDirectory() && toResolve(file, myProject)) { int fileId = getAbsId(file); int i = totalSize - toProcess.size(); indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl()); int[] forwardIds = processFile(file, fileId, indicator); if (forwardIds == null) { // queueUpdate(file); return false; } fileToForwardIds.put(fileId, forwardIds); } toProcess.remove(file); return true; } catch (RuntimeException e) { indicator.checkCanceled(); } return true; }; boolean success = true; try { success = processFilesConcurrently(files, indicator, processor); } finally { this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size(); queue(toProcess, "re-added after fail. success=" + success); storeIds(fileToForwardIds); long end = System.currentTimeMillis(); log( "Resolved batch of " + (totalSize - toProcess.size()) + " from " + totalSize + " files in " + ((end - start) / 1000) + "sec. (Gap: " + storage.gap + ")"); synchronized (filesToResolve) { upToDate = filesToResolve.isEmpty(); log("upToDate = " + upToDate); if (upToDate) { for (Listener listener : myListeners) { listener.allFilesResolved(); } } } } }
public abstract class Client extends UserDataHolderBase { static final RuntimeException REJECTED = Promise.createError("rejected"); protected final Channel channel; final ConcurrentIntObjectMap<AsyncPromise<Object>> messageCallbackMap = ContainerUtil.createConcurrentIntObjectMap(); protected Client(@NotNull Channel channel) { this.channel = channel; } @NotNull public final EventLoop getEventLoop() { return channel.eventLoop(); } @NotNull public final ByteBufAllocator getByteBufAllocator() { return channel.alloc(); } protected abstract ChannelFuture send(@NotNull ByteBuf message); public abstract void sendHeartbeat(); @Nullable final <T> AsyncPromise<T> send(int messageId, @NotNull ByteBuf message) { ChannelFuture channelFuture = send(message); if (messageId == -1) { return null; } ChannelFutureAwarePromise<T> promise = new ChannelFutureAwarePromise<T>(messageId, messageCallbackMap); channelFuture.addListener(promise); //noinspection unchecked messageCallbackMap.put(messageId, (AsyncPromise<Object>) promise); return promise; } final void rejectAsyncResults(@NotNull ExceptionHandler exceptionHandler) { if (!messageCallbackMap.isEmpty()) { Enumeration<AsyncPromise<Object>> elements = messageCallbackMap.elements(); while (elements.hasMoreElements()) { try { elements.nextElement().setError(REJECTED); } catch (Throwable e) { exceptionHandler.exceptionCaught(e); } } } } private static final class ChannelFutureAwarePromise<T> extends AsyncPromise<T> implements ChannelFutureListener { private final int messageId; private final ConcurrentIntObjectMap<?> messageCallbackMap; public ChannelFutureAwarePromise(int messageId, ConcurrentIntObjectMap<?> messageCallbackMap) { this.messageId = messageId; this.messageCallbackMap = messageCallbackMap; } @Override public boolean setError(@NotNull Throwable error) { boolean result = super.setError(error); messageCallbackMap.remove(messageId); return result; } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { Throwable cause = future.cause(); setError(cause == null ? Promise.createError("No success") : cause); } } } }
/** Author: dmitrylomov */ public class ModuleScopeProviderImpl implements ModuleScopeProvider { private final Module myModule; private final ConcurrentIntObjectMap<GlobalSearchScope> myScopeCache = ContainerUtil.createConcurrentIntObjectMap(); private ModuleWithDependentsTestScope myModuleTestsWithDependentsScope; public ModuleScopeProviderImpl(@NotNull Module module) { myModule = module; } @NotNull private GlobalSearchScope getCachedScope(@ModuleWithDependenciesScope.ScopeConstant int options) { GlobalSearchScope scope = myScopeCache.get(options); if (scope == null) { scope = new ModuleWithDependenciesScope(myModule, options); myScopeCache.put(options, scope); } return scope; } @Override @NotNull public GlobalSearchScope getModuleScope() { return getCachedScope(ModuleWithDependenciesScope.COMPILE | ModuleWithDependenciesScope.TESTS); } @NotNull @Override public GlobalSearchScope getModuleScope(boolean includeTests) { return getCachedScope( ModuleWithDependenciesScope.COMPILE | (includeTests ? ModuleWithDependenciesScope.TESTS : 0)); } @Override @NotNull public GlobalSearchScope getModuleWithLibrariesScope() { return getCachedScope( ModuleWithDependenciesScope.COMPILE | ModuleWithDependenciesScope.TESTS | ModuleWithDependenciesScope.LIBRARIES); } @Override @NotNull public GlobalSearchScope getModuleWithDependenciesScope() { return getCachedScope( ModuleWithDependenciesScope.COMPILE | ModuleWithDependenciesScope.TESTS | ModuleWithDependenciesScope.MODULES); } @NotNull @Override public GlobalSearchScope getModuleContentScope() { return getCachedScope(ModuleWithDependenciesScope.CONTENT); } @NotNull @Override public GlobalSearchScope getModuleContentWithDependenciesScope() { return getCachedScope( ModuleWithDependenciesScope.CONTENT | ModuleWithDependenciesScope.MODULES); } @Override @NotNull public GlobalSearchScope getModuleWithDependenciesAndLibrariesScope(boolean includeTests) { return getCachedScope( ModuleWithDependenciesScope.COMPILE | ModuleWithDependenciesScope.MODULES | ModuleWithDependenciesScope.LIBRARIES | (includeTests ? ModuleWithDependenciesScope.TESTS : 0)); } @Override @NotNull public GlobalSearchScope getModuleWithDependentsScope() { return getModuleTestsWithDependentsScope().getBaseScope(); } @Override @NotNull public ModuleWithDependentsTestScope getModuleTestsWithDependentsScope() { ModuleWithDependentsTestScope scope = myModuleTestsWithDependentsScope; if (scope == null) { myModuleTestsWithDependentsScope = scope = new ModuleWithDependentsTestScope(myModule); } return scope; } @Override @NotNull public GlobalSearchScope getModuleRuntimeScope(boolean includeTests) { return getCachedScope( ModuleWithDependenciesScope.MODULES | ModuleWithDependenciesScope.LIBRARIES | (includeTests ? ModuleWithDependenciesScope.TESTS : 0)); } @Override public void clearCache() { myScopeCache.clear(); myModuleTestsWithDependentsScope = null; } }
// todo send FCGI_ABORT_REQUEST if client channel disconnected public abstract class FastCgiService extends SingleConnectionNetService { protected static final Logger LOG = Logger.getInstance(FastCgiService.class); private final AtomicInteger requestIdCounter = new AtomicInteger(); protected final ConcurrentIntObjectMap<Channel> requests = ContainerUtil.createConcurrentIntObjectMap(); public FastCgiService(@NotNull Project project) { super(project); } @Override protected void closeProcessConnections() { try { super.closeProcessConnections(); } finally { requestIdCounter.set(0); if (!requests.isEmpty()) { List<Channel> waitingClients = ContainerUtil.toList(requests.elements()); requests.clear(); for (Channel channel : waitingClients) { sendBadGateway(channel); } } } } private static void sendBadGateway(@NotNull Channel channel) { try { if (channel.isActive()) { Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel); } } catch (Throwable e) { NettyUtil.log(e, LOG); } } @Override protected void configureBootstrap( @NotNull Bootstrap bootstrap, @NotNull final Consumer<String> errorOutputConsumer) { bootstrap.handler( new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { channel .pipeline() .addLast( "fastCgiDecoder", new FastCgiDecoder(errorOutputConsumer, FastCgiService.this)); channel.pipeline().addLast("exceptionHandler", ChannelExceptionHandler.getInstance()); } }); } public void send(@NotNull final FastCgiRequest fastCgiRequest, @NotNull ByteBuf content) { final ByteBuf notEmptyContent; if (content.isReadable()) { content.retain(); notEmptyContent = content; notEmptyContent.touch(); } else { notEmptyContent = null; } try { if (processHandler.has()) { fastCgiRequest.writeToServerChannel(notEmptyContent, processChannel); } else { processHandler .get() .done( new Consumer<OSProcessHandler>() { @Override public void consume(OSProcessHandler osProcessHandler) { fastCgiRequest.writeToServerChannel(notEmptyContent, processChannel); } }) .rejected( new Consumer<Throwable>() { @Override public void consume(Throwable error) { LOG.error(error); handleError(fastCgiRequest, notEmptyContent); } }); } } catch (Throwable e) { LOG.error(e); handleError(fastCgiRequest, notEmptyContent); } } private void handleError(@NotNull FastCgiRequest fastCgiRequest, @Nullable ByteBuf content) { try { if (content != null && content.refCnt() != 0) { content.release(); } } finally { Channel channel = requests.remove(fastCgiRequest.requestId); if (channel != null) { sendBadGateway(channel); } } } public int allocateRequestId(@NotNull Channel channel) { int requestId = requestIdCounter.getAndIncrement(); if (requestId >= Short.MAX_VALUE) { requestIdCounter.set(0); requestId = requestIdCounter.getAndDecrement(); } requests.put(requestId, channel); return requestId; } void responseReceived(int id, @Nullable ByteBuf buffer) { Channel channel = requests.remove(id); if (channel == null || !channel.isActive()) { if (buffer != null) { buffer.release(); } return; } if (buffer == null) { Responses.sendStatus(HttpResponseStatus.BAD_GATEWAY, channel); return; } HttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buffer); try { parseHeaders(httpResponse, buffer); Responses.addServer(httpResponse); if (!HttpHeaderUtil.isContentLengthSet(httpResponse)) { HttpHeaderUtil.setContentLength(httpResponse, buffer.readableBytes()); } } catch (Throwable e) { buffer.release(); try { LOG.error(e); } finally { Responses.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, channel); } return; } channel.writeAndFlush(httpResponse); } private static void parseHeaders(@NotNull HttpResponse response, @NotNull ByteBuf buffer) { StringBuilder builder = new StringBuilder(); while (buffer.isReadable()) { builder.setLength(0); String key = null; boolean valueExpected = true; while (true) { int b = buffer.readByte(); if (b < 0 || b == '\n') { break; } if (b != '\r') { if (valueExpected && b == ':') { valueExpected = false; key = builder.toString(); builder.setLength(0); MessageDecoder.skipWhitespace(buffer); } else { builder.append((char) b); } } } if (builder.length() == 0) { // end of headers return; } // skip standard headers if (StringUtil.isEmpty(key) || StringUtilRt.startsWithIgnoreCase(key, "http") || StringUtilRt.startsWithIgnoreCase(key, "X-Accel-")) { continue; } String value = builder.toString(); if (key.equalsIgnoreCase("status")) { int index = value.indexOf(' '); if (index == -1) { LOG.warn("Cannot parse status: " + value); response.setStatus(HttpResponseStatus.OK); } else { response.setStatus( HttpResponseStatus.valueOf(Integer.parseInt(value.substring(0, index)))); } } else if (!(key.startsWith("http") || key.startsWith("HTTP"))) { response.headers().add(key, value); } } } }