Skip to content

Commit

Permalink
feat(graalvm): experimental prototype for hybrid vfs.
Browse files Browse the repository at this point in the history
Signed-off-by: Dario Valdespino <dvaldespino00@gmail.com>
  • Loading branch information
darvld committed Nov 1, 2023
1 parent 4f1c2f7 commit 66a26a4
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ package elide.runtime.plugins.vfs

import org.graalvm.polyglot.io.FileSystem
import org.graalvm.polyglot.io.IOAccess
import java.net.URI
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import elide.runtime.Logging
import elide.runtime.core.*
import elide.runtime.core.EngineLifecycleEvent.ContextCreated
import elide.runtime.core.EngineLifecycleEvent.EngineCreated
Expand All @@ -23,8 +27,9 @@ import elide.runtime.core.EnginePlugin.Key
import elide.runtime.core.PolyglotEngineConfiguration.HostAccess
import elide.runtime.core.PolyglotEngineConfiguration.HostAccess.ALLOW_ALL
import elide.runtime.core.PolyglotEngineConfiguration.HostAccess.ALLOW_IO
import elide.runtime.gvm.vfs.EmbeddedGuestVFS
import elide.runtime.gvm.vfs.HostVFS
import elide.runtime.gvm.internals.vfs.EmbeddedGuestVFSImpl
import elide.runtime.gvm.internals.vfs.HostVFSImpl
import elide.runtime.plugins.vfs.internal.HybridVfs

/**
* Engine plugin providing configurable VFS support for polyglot contexts. Both embedded and host VFS implementations
Expand All @@ -43,22 +48,28 @@ import elide.runtime.gvm.vfs.HostVFS
* ```
*/
@DelicateElideApi public class Vfs private constructor(public val config: VfsConfig) {
/** Plugin logger instance. */
private val logging by lazy { Logging.of(Vfs::class) }

/** Pre-configured VFS, created when the engine is initialized. */
private lateinit var fileSystem: FileSystem

internal fun onEngineCreated(@Suppress("unused_parameter") builder: PolyglotEngineBuilder) {
// select the VFS implementation depending on the configuration
fileSystem = when (config.useHost) {
true -> when (config.writable) {
true -> HostVFS.acquireWritable()
false -> HostVFS.acquire()
}

false -> when (config.writable) {
true -> EmbeddedGuestVFS.writable(config.registeredBundles)
else -> EmbeddedGuestVFS.forBundles(config.registeredBundles)
}
val embedded = acquireEmbeddedVfs(config.writable, config.registeredBundles)

// if no host access is requested, use an embedded in-memory vfs
if (!config.useHost) {
logging.debug("No host access requested, using in-memory vfs")

fileSystem = embedded
return
}

// if the configuration requires host access, we use a hybrid vfs
logging.debug("Host access requested, using hybrid vfs")
val host = acquireHostVfs(config.writable)
fileSystem = HybridVfs(host, embedded)
}

/** Configure a context builder to use a custom [fileSystem]. */
Expand Down Expand Up @@ -88,6 +99,25 @@ import elide.runtime.gvm.vfs.HostVFS
}

private val HostAccess.useHostFs get() = this == ALLOW_IO || this == ALLOW_ALL

/** Build a new [FileSystem] delegating to the host FS. */
private fun acquireHostVfs(writable: Boolean): FileSystem {
return HostVFSImpl.Builder.newBuilder()
.setReadOnly(!writable)
.setWorkingDirectory(Path.of(".").absolutePathString())
.build()
}

/** Build a new embedded [FileSystem], optionally [writable], with the specified [root] path and [bundles]. */
private fun acquireEmbeddedVfs(
writable: Boolean,
bundles: List<URI>
): FileSystem {
return EmbeddedGuestVFSImpl.Builder.newBuilder()
.setBundlePaths(bundles)
.setReadOnly(!writable)
.build()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package elide.runtime.plugins.vfs.internal

import org.graalvm.polyglot.io.FileSystem
import java.net.URI
import java.nio.channels.SeekableByteChannel
import java.nio.file.*
import java.nio.file.DirectoryStream.Filter
import java.nio.file.attribute.FileAttribute
import kotlin.io.path.pathString

internal class HybridVfs(
private val host: FileSystem,
private val inMemory: FileSystem,
) : FileSystem {
private inline fun <R> withHostFallback(op: (FileSystem) -> R): R {
return runCatching { op(inMemory) }.getOrElse { op(host) }
}

private fun Path.compatibleWith(fileSystem: FileSystem): Path {
return fileSystem.parsePath(pathString)
}

override fun parsePath(uri: URI?): Path = host.parsePath(uri)

override fun parsePath(path: String?): Path = host.parsePath(path)

override fun checkAccess(
path: Path?,
modes: MutableSet<out AccessMode>?,
vararg linkOptions: LinkOption?
) = withHostFallback {
it.checkAccess(path?.compatibleWith(it), modes, *linkOptions)
}

override fun createDirectory(dir: Path?, vararg attrs: FileAttribute<*>?) {
return withHostFallback { it.createDirectory(dir?.compatibleWith(it), *attrs) }
}

override fun delete(path: Path?) {
withHostFallback { it.delete(path?.compatibleWith(it)) }
}

override fun newByteChannel(
path: Path?,
options: MutableSet<out OpenOption>?,
vararg attrs: FileAttribute<*>?
): SeekableByteChannel = withHostFallback {
it.newByteChannel(path?.compatibleWith(it), options, *attrs)
}

override fun newDirectoryStream(dir: Path?, filter: Filter<in Path>?): DirectoryStream<Path> = withHostFallback {
it.newDirectoryStream(dir?.compatibleWith(it), filter)
}

override fun toAbsolutePath(path: Path?): Path {
return host.toAbsolutePath(path)
}

override fun toRealPath(path: Path?, vararg linkOptions: LinkOption?): Path {
return host.toRealPath(path)
}

override fun readAttributes(
path: Path?,
attributes: String?,
vararg options: LinkOption?
): MutableMap<String, Any> = withHostFallback {
it.readAttributes(path?.compatibleWith(it), attributes, *options)
}
}

0 comments on commit 66a26a4

Please sign in to comment.