Skip to content

Commit

Permalink
feat(core): Support resource isolation
Browse files Browse the repository at this point in the history
  • Loading branch information
fangyinc committed Nov 5, 2024
1 parent b95379b commit d8af8ac
Show file tree
Hide file tree
Showing 27 changed files with 1,240 additions and 189 deletions.
139 changes: 70 additions & 69 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ default-members = [
]

[workspace.package]
version = "0.1.3"
version = "0.1.4-rc0"
authors = ["fangyinc <[email protected]>"]
edition = "2021"
homepage = "https://github.com/fangyinc/lyric"
Expand Down Expand Up @@ -43,9 +43,10 @@ tokio-util = { version = "0.7.12", features = ["io"] }
tokio-stream = { version = "0.1.16", features = ["io-util"] }
pyo3 = { version = "0.22.5" }
python3-dll-a = "0.2.10"
bitflags = "2.6"

lyric = { version = "0.1.3", path = "crates/lyric", default-features = false }
lyric-utils = { version = "0.1.3", path = "crates/lyric-utils", default-features = false }
lyric-rpc = { version = "0.1.3", path = "crates/lyric-rpc", default-features = false }
lyric-wasm-runtime = { version = "0.1.3", path = "crates/lyric-wasm-runtime", default-features = false }
lyric-py = { version = "0.1.3", path = "bindings/python/lyric-py", default-features = false }
lyric = { version = "0.1.4-rc0", path = "crates/lyric", default-features = false }
lyric-utils = { version = "0.1.4-rc0", path = "crates/lyric-utils", default-features = false }
lyric-rpc = { version = "0.1.4-rc0", path = "crates/lyric-rpc", default-features = false }
lyric-wasm-runtime = { version = "0.1.4-rc0", path = "crates/lyric-wasm-runtime", default-features = false }
lyric-py = { version = "0.1.4-rc0", path = "bindings/python/lyric-py", default-features = false }
120 changes: 88 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ A Rust-powered secure sandbox for multi-language code execution, leveraging WebA
**Install Lyric via pip:**

```bash
pip install "lyric-py>=0.1.3"
pip install "lyric-py>=0.1.4-rc0"
```

**Install default Python webassembly worker:**

```bash
pip install "lyric-py-worker>=0.1.3"
pip install "lyric-py-worker>=0.1.4-rc0"
```

**Install default JavaScript webassembly worker:**

```bash
pip install "lyric-js-worker>=0.1.3"
pip install "lyric-js-worker>=0.1.4-rc0"
```

### Basic Usage
Expand All @@ -42,6 +42,17 @@ pip install "lyric-js-worker>=0.1.3"
import asyncio
from lyric import DefaultLyricDriver

python_code = """
def add(a, b):
return a + b
result = add(1, 2)
print(result)
"""

js_code = """
console.log('Hello from JavaScript!');
"""

async def main():
lcd = DefaultLyricDriver(host="localhost", log_level="ERROR")
lcd.start()
Expand All @@ -50,21 +61,10 @@ async def main():
await lcd.lyric.load_default_workers()

# Execute Python code
python_code = """
def add(a, b):
return a + b
result = add(1, 2)
print(result)
"""

py_res = await lcd.exec(python_code, "python")
print(py_res)

# Execute JavaScript code
js_code = """
console.log('Hello from JavaScript!');
"""

js_res = await lcd.exec(js_code, "javascript")
print(js_res)

Expand All @@ -81,13 +81,7 @@ import asyncio
import json
from lyric import DefaultLyricDriver

async def main():
lcd = DefaultLyricDriver(host="localhost", log_level="ERROR")
lcd.start()

# Load workers(default: Python, JavaScript)
await lcd.lyric.load_default_workers()
py_func = """
py_func = """
def message_handler(message_dict):
user_message = message_dict.get("user_message")
ai_message = message_dict.get("ai_message")
Expand All @@ -99,18 +93,8 @@ def message_handler(message_dict):
"handler_language": "python",
}
"""
input_data = {
"user_message": "Hello from user",
"ai_message": "Hello from AI",
}
input_bytes = json.dumps(input_data).encode("utf-8")
py_res = await lcd.exec1(py_func, input_bytes, "message_handler", lang="python")
# Get the result of the function execution
result_dict = py_res.output
print("Python result:", result_dict)
print(f"Full output: {py_res}")

js_func = """
js_func = """
function message_handler(message_dict) {
return {
user: message_dict.user_message,
Expand All @@ -121,6 +105,25 @@ function message_handler(message_dict) {
};
}
"""
async def main():
lcd = DefaultLyricDriver(host="localhost", log_level="ERROR")
lcd.start()

# Load workers(default: Python, JavaScript)
await lcd.lyric.load_default_workers()

input_data = {
"user_message": "Hello from user",
"ai_message": "Hello from AI",
}
input_bytes = json.dumps(input_data).encode("utf-8")

py_res = await lcd.exec1(py_func, input_bytes, "message_handler", lang="python")
# Get the result of the function execution
result_dict = py_res.output
print("Python result:", result_dict)
print(f"Full output: {py_res}")

js_res = await lcd.exec1(js_func, input_bytes, "message_handler", lang="javascript")
# Get the result of the function execution
result_dict = js_res.output
Expand All @@ -133,6 +136,59 @@ function message_handler(message_dict) {
asyncio.run(main())
```

## Advanced Usage

### Execution With Specific Resources

```python
import asyncio
from lyric import DefaultLyricDriver, PyTaskResourceConfig, PyTaskFsConfig, PyTaskMemoryConfig

lcd = DefaultLyricDriver(host="localhost", log_level="ERROR")
lcd.start()

python_code = """
import os
# List the files in the root directory
root = os.listdir('/tmp/')
print("Files in the root directory:", root)
# Create a new file in the home directory
with open('/home/new_file.txt', 'w') as f:
f.write('Hello, World!')
"""

async def main():
# Load workers(default: Python, JavaScript)
await lcd.lyric.load_default_workers()

dir_read, dir_write = 1, 2
file_read, file_write = 3, 4
resources = PyTaskResourceConfig(
fs=PyTaskFsConfig(
preopens=[
# Mount current directory in host to "/tmp" in the sandbox with read permission
(".", "/tmp", dir_read, file_read),
# Mount "/tmp" in host to "/home" in the sandbox with read and write permission
("/tmp", "/home", dir_read | dir_write, file_read | file_write),
]
),
memory=PyTaskMemoryConfig(
# Set the memory limit to 30MB
memory_limit=30 * 1024 * 1024 # 30MB in bytes
)
)

py_res = await lcd.exec(python_code, "python", resources=resources)
assert py_res.exit_code == 0, "Python code should exit with 0"

# Stop the driver
lcd.stop()

asyncio.run(main())
```

## Examples

- [Notebook-Qick Start](examples/notebook/lyric_quick_start.ipynb): A Jupyter notebook demonstrating how to use Lyric to execute Python and JavaScript code.
Expand Down
85 changes: 84 additions & 1 deletion bindings/javascript/lyric-js-worker/wit/deps/task/types.wit
Original file line number Diff line number Diff line change
@@ -1,11 +1,92 @@
interface types {

/// CPU Resource Configuration
record cpu-config {
/// CPU core limit (e.g. 1.0 represents a full core, 0.5 represents half a core)
cpu-cores: option<f32>,
/// CPU share value (similar to --cpu-shares in docker, default 1024)
cpu-shares: option<u32>,
/// CPU period configuration (microseconds, at most use quota microseconds within period)
cpu-quota: option<u32>,
/// CPU period time (microseconds, default 100000, i.e. 100ms)
cpu-period: option<u32>
}

/// Memory Resource Configuration
record memory-config {
/// Memory limit in bytes
memory-limit: option<u64>
}

/// Network Resource Configuration
record network-config {
/// Whether to enable network access
enable-network: bool,
/// Inbound bandwidth limit (KB/s)
ingress-limit-kbps: option<u32>,
/// Outbound bandwidth limit (KB/s)
egress-limit-kbps: option<u32>,
/// Allowed host list
allowed-hosts: option<list<string>>,
/// Allowed port range list (start_port, end_port)
allowed-ports: option<list<tuple<u16, u16>>>
}

/// File system permission
flags file-perms {
/// Read permission
read,
/// Write permission
write,
}

/// File system configuration
record fs-config {
/// Pre-mapped directory list (host path, container path, directory permissions, file permissions)
preopens: list<tuple<string, string, file-perms, file-perms>>,
/// File system size limit in bytes
fs-size-limit: option<u64>,
/// Temporary directory for wasi
temp-dir: option<string>
}

/// Instance limits
record instance-limits {
// Max number of instances
max-instances: option<u32>,
/// Max number of tables
max-tables: option<u32>,
/// Max number of elements in each table
max-table-elements: option<u32>
}

/// Full resource configuration
record resource-config {
/// CPU configuration
cpu: option<cpu-config>,
/// Memory configuration
memory: option<memory-config>,
/// Network configuration
network: option<network-config>,
/// File system configuration
fs: option<fs-config>,
// Instance limits
instance: option<instance-limits>,
/// Task timeout in milliseconds
timeout-ms: option<u32>,
/// List of environment variables
env-vars: list<tuple<string, string>>
}

/// A request to interpret a script
record interpreter-request {
/// Resource configuration
resources: option<resource-config>,
/// The protocol version of the interpreter
protocol: u32,
/// The language of the script
lang: string,
/// The script to be interpreted
/// The script to be interpreted
code: string
}

Expand All @@ -29,6 +110,8 @@ interface types {
}

record binary-request {
/// Resource configuration
resources: option<resource-config>,
protocol: u32,
data: list<u8>
}
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/lyric-js-worker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lyric-js-worker"
version = "0.1.3"
version = "0.1.4-rc0"
description = "Add your description here"
authors = [
{ name = "Fangyin Cheng", email = "[email protected]" },
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/lyric-py-worker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lyric-py-worker"
version = "0.1.3"
version = "0.1.4-rc0"
description = "Add your description here"
authors = [
{ name = "Fangyin Cheng", email = "[email protected]" },
Expand Down
Loading

0 comments on commit d8af8ac

Please sign in to comment.