Skip to content

Commit 2c959e5

Browse files
committed
Add detailed logging to Python soname patcher
- Enhanced logging in fix-python-soname.js to track WASM execution - Added verbose logging to Rust soname patcher for debugging - Added Python library discovery diagnostics in CI pipeline - Track file operations and show backup creation status This will help diagnose why the soname patcher might not be working correctly in the CI environment.
1 parent a653799 commit 2c959e5

File tree

4 files changed

+185
-16
lines changed

4 files changed

+185
-16
lines changed

.github/workflows/CI.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,22 @@ jobs:
350350
# Fix Python soname if WASI module is available
351351
if [ -f "fix-python-soname.wasm" ] && [ -f "fix-python-soname.js" ]; then
352352
echo "Fixing Python soname in .node file..."
353+
# Debug: Check Python version and libraries available
354+
echo "Checking Python installation..."
355+
which python3 || true
356+
python3 --version || true
357+
echo "Looking for Python libraries..."
358+
find /usr/lib -name "libpython*.so*" 2>/dev/null | head -20 || true
359+
# Run the soname fixer
353360
node fix-python-soname.js || echo "Warning: Failed to fix Python soname"
361+
# Check if backup was created
362+
if [ -f "*.node.bak" ]; then
363+
echo "Backup file created, soname fix was attempted"
364+
ls -la *.node.bak || true
365+
fi
366+
else
367+
echo "Soname fixer not found, skipping..."
368+
ls -la fix-python-soname.* || true
354369
fi
355370
356371
pnpm test

STREAM_AND_WS.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# STREAM_AND_WS.md
2+
3+
## Future Design: Streaming and WebSocket Support
4+
5+
This document captures design ideas for adding streaming HTTP responses and WebSocket support after the basic HTTP implementation is complete.
6+
7+
### Streaming Response Support
8+
9+
For streaming responses, we can add a separate method that returns a stream:
10+
11+
```rust
12+
#[napi]
13+
impl PythonHandler {
14+
#[napi]
15+
pub async fn handle_request_stream(&self, request: Request) -> Result<ResponseStream> {
16+
let (tx_sender, tx_receiver) = self.event_loop
17+
.start_streaming_request(request)
18+
.await?;
19+
20+
Ok(ResponseStream { receiver: tx_receiver })
21+
}
22+
}
23+
24+
#[napi]
25+
pub struct ResponseStream {
26+
receiver: mpsc::Receiver<Bytes>,
27+
}
28+
29+
#[napi]
30+
impl ResponseStream {
31+
#[napi]
32+
pub async fn next_chunk(&mut self) -> Result<Option<Buffer>> {
33+
match self.receiver.recv().await {
34+
Some(bytes) => Ok(Some(bytes.into())),
35+
None => Ok(None),
36+
}
37+
}
38+
}
39+
```
40+
41+
### WebSocket with Async
42+
43+
```rust
44+
#[napi]
45+
impl PythonHandler {
46+
#[napi]
47+
pub async fn handle_websocket(&self, request: WebSocketRequest) -> Result<WebSocketConnection> {
48+
let connection = self.event_loop.create_websocket(request).await?;
49+
Ok(connection)
50+
}
51+
}
52+
53+
#[napi]
54+
pub struct WebSocketConnection {
55+
id: u64,
56+
tx: mpsc::Sender<WebSocketMessage>,
57+
rx: mpsc::Receiver<WebSocketMessage>,
58+
}
59+
60+
#[napi]
61+
impl WebSocketConnection {
62+
#[napi]
63+
pub async fn send(&self, message: String) -> Result<()> {
64+
self.tx.send(WebSocketMessage::Text(message)).await
65+
.map_err(|_| Error::from_reason("Failed to send"))?;
66+
Ok(())
67+
}
68+
69+
#[napi]
70+
pub async fn receive(&mut self) -> Result<Option<String>> {
71+
match self.rx.recv().await {
72+
Some(WebSocketMessage::Text(text)) => Ok(Some(text)),
73+
Some(WebSocketMessage::Binary(_)) => Ok(Some("[binary data]".to_string())),
74+
None => Ok(None),
75+
}
76+
}
77+
}
78+
```
79+
80+
### JavaScript Usage Examples
81+
82+
```javascript
83+
// Streaming
84+
const stream = await handler.handleRequestStream(request);
85+
let chunk;
86+
while ((chunk = await stream.nextChunk()) !== null) {
87+
console.log('Received chunk:', chunk);
88+
}
89+
90+
// WebSocket
91+
const ws = await handler.handleWebsocket(wsRequest);
92+
await ws.send('Hello Python!');
93+
const message = await ws.receive();
94+
```
95+
96+
### Implementation Notes
97+
98+
1. **Streaming**: The key is to return a handle to the stream rather than waiting for the complete response
99+
2. **WebSocket**: Requires bidirectional channels and persistent connection state
100+
3. **Backpressure**: Both implementations should respect backpressure through channel bounds
101+
4. **Error Handling**: Connection errors need to be propagated through the stream/websocket interface
102+
103+
These features can be added incrementally after the basic HTTP request/response cycle is working correctly with the Python event loop architecture.

fix-python-soname.js

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function findNodeFile() {
2222

2323
// Only run on Linux - other platforms don't need soname fixing
2424
if (platform !== 'linux') {
25-
console.log(`Skipping soname fix on ${platform} - only needed on Linux`);
25+
console.log(`fix-python-soname: Skipping soname fix on ${platform} - only needed on Linux`);
2626
return null;
2727
}
2828

@@ -34,7 +34,7 @@ function findNodeFile() {
3434

3535
const target = archMap[arch];
3636
if (!target) {
37-
console.log(`Unsupported architecture: ${arch}`);
37+
console.log(`fix-python-soname: Unsupported architecture: ${arch}`);
3838
return null;
3939
}
4040

@@ -53,20 +53,29 @@ function findNodeFile() {
5353
.map(f => path.join(__dirname, f))
5454
];
5555

56-
console.log('Searching for .node files in:');
56+
console.log('fix-python-soname: Searching for .node files in:');
5757
console.log(` Current directory: ${__dirname}`);
5858
console.log(' Possible paths:');
5959
possiblePaths.forEach(p => console.log(` - ${p}`));
6060

6161
for (const nodePath of possiblePaths) {
6262
if (fs.existsSync(nodePath)) {
63-
console.log(`Found .node file: ${nodePath}`);
63+
console.log(`fix-python-soname: Found .node file: ${nodePath}`);
64+
// Check if file is readable and has proper permissions
65+
try {
66+
const stats = fs.statSync(nodePath);
67+
console.log(` File size: ${stats.size} bytes`);
68+
console.log(` File permissions: ${stats.mode.toString(8)}`);
69+
console.log(` File modified: ${stats.mtime}`);
70+
} catch (err) {
71+
console.error(` Error getting file stats: ${err.message}`);
72+
}
6473
return nodePath;
6574
}
6675
}
6776

68-
console.log('No .node file found for current platform');
69-
console.log('Directory contents:', fs.readdirSync(__dirname).filter(f => !f.startsWith('.')));
77+
console.log('fix-python-soname: No .node file found for current platform');
78+
console.log('fix-python-soname: Directory contents:', fs.readdirSync(__dirname).filter(f => !f.startsWith('.')));
7079
return null;
7180
}
7281

@@ -76,17 +85,20 @@ if (!nodeFilePath) {
7685
process.exit(0); // Exit silently if not applicable
7786
}
7887

79-
console.log(`Fixing Python soname for: ${nodeFilePath}`);
88+
console.log(`fix-python-soname: Preparing to fix Python soname for: ${nodeFilePath}`);
8089

8190
// Create a WASI instance
8291
const wasi = new WASI({
92+
version: 'preview1', // Required for newer Node.js versions
8393
args: ['fix-python-soname', nodeFilePath],
8494
env: process.env,
8595
preopens: {
8696
'/': '/', // Give access to the filesystem
8797
}
8898
});
8999

100+
console.log('fix-python-soname: WASI instance created successfully');
101+
90102
// Load and instantiate the WASM module
91103
// Try multiple possible locations for the WASM file
92104
const possibleWasmPaths = [
@@ -103,31 +115,48 @@ for (const tryPath of possibleWasmPaths) {
103115
}
104116

105117
if (!wasmPath) {
106-
console.error('WASM module not found in any of the expected locations:');
118+
console.error('fix-python-soname: WASM module not found in any of the expected locations:');
107119
possibleWasmPaths.forEach(p => console.error(` - ${p}`));
108120
process.exit(1);
109121
}
110122

111-
console.log('Loading WASM soname patcher...');
123+
console.log(`fix-python-soname: Loading WASM module from: ${wasmPath}`);
112124

113125
async function runSonameFixer() {
114126
try {
127+
console.log('fix-python-soname: Reading WASM file...');
115128
const wasm = fs.readFileSync(wasmPath);
129+
console.log(`fix-python-soname: WASM file loaded, size: ${wasm.length} bytes`);
130+
131+
console.log('fix-python-soname: Instantiating WebAssembly module...');
116132
const { instance } = await WebAssembly.instantiate(wasm, {
117133
wasi_snapshot_preview1: wasi.wasiImport
118134
});
135+
console.log('fix-python-soname: WebAssembly module instantiated successfully');
119136

120137
// Run the WASI module
138+
console.log('fix-python-soname: Starting WASI execution...');
121139
const exitCode = wasi.start(instance);
140+
console.log(`fix-python-soname: WASI execution completed with exit code: ${exitCode}`);
122141

123142
if (exitCode === 0) {
124143
console.log('✅ Python soname successfully fixed');
144+
145+
// Verify the fix by checking if backup was created
146+
const backupPath = nodeFilePath.replace('.node', '.node.bak');
147+
if (fs.existsSync(backupPath)) {
148+
console.log(`fix-python-soname: Backup created at: ${backupPath}`);
149+
const originalSize = fs.statSync(backupPath).size;
150+
const newSize = fs.statSync(nodeFilePath).size;
151+
console.log(`fix-python-soname: Original size: ${originalSize}, New size: ${newSize}`);
152+
}
125153
} else {
126154
console.error(`❌ Soname fixer exited with code: ${exitCode}`);
127155
process.exit(exitCode);
128156
}
129157
} catch (err) {
130158
console.error('❌ Error running soname fixer:', err.message);
159+
console.error('fix-python-soname: Full error:', err);
131160

132161
// Don't fail the install if soname fixing fails - it's a best-effort operation
133162
console.log('⚠️ Continuing without soname fix. Python integration may not work correctly.');

fix-python-soname/src/main.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use arwen::elf::ElfContainer;
22
use std::{collections::HashMap, env, fs::{self, File}, path::Path};
33

44
fn find_python_library() -> Result<String, String> {
5+
eprintln!("fix-python-soname: Starting search for Python library...");
6+
57
// Generate Python versions from 3.20 down to 3.8
68
let mut python_versions = Vec::new();
79
for major in (8..=20).rev() {
@@ -15,6 +17,8 @@ fn find_python_library() -> Result<String, String> {
1517
python_versions.push(format!("libpython3.{}m.so", major));
1618
}
1719

20+
eprintln!("fix-python-soname: Looking for versions: {:?}", &python_versions[0..6]);
21+
1822
// Get system architecture
1923
let arch = std::env::consts::ARCH;
2024
let arch_triplet = match arch {
@@ -178,17 +182,22 @@ fn find_python_library() -> Result<String, String> {
178182
}
179183
}
180184

185+
eprintln!("fix-python-soname: Searching in {} directories...", lib_paths.len());
186+
eprintln!("fix-python-soname: First 5 paths: {:?}", &lib_paths[0..5.min(lib_paths.len())]);
187+
181188
// First try exact version matches
182189
for lib_name in &python_versions {
183190
for lib_path in &lib_paths {
184191
let full_path = format!("{}/{}", lib_path, lib_name);
185192
if Path::new(&full_path).exists() {
186-
eprintln!("Found Python library: {}", lib_name);
193+
eprintln!("fix-python-soname: Found Python library: {} at {}", lib_name, full_path);
187194
return Ok(lib_name.to_string());
188195
}
189196
}
190197
}
191198

199+
eprintln!("fix-python-soname: No exact match found, searching for any libpython*.so files...");
200+
192201
// If no exact match found, search directories for any libpython*.so
193202
for lib_path in &lib_paths {
194203
if let Ok(entries) = fs::read_dir(lib_path) {
@@ -214,7 +223,7 @@ fn find_python_library() -> Result<String, String> {
214223
found_libs.sort_by(|a, b| b.2.cmp(&a.2).then(b.1.cmp(&a.1)));
215224

216225
if let Some((lib_name, _, _)) = found_libs.first() {
217-
eprintln!("Found Python library: {}", lib_name);
226+
eprintln!("fix-python-soname: Found Python library: {} in {}", lib_name, lib_path);
218227
return Ok(lib_name.clone());
219228
}
220229
}
@@ -226,68 +235,81 @@ fn find_python_library() -> Result<String, String> {
226235
}
227236

228237
fn main() -> Result<(), Box<dyn std::error::Error>> {
238+
eprintln!("fix-python-soname: Starting soname patcher...");
239+
229240
let args: Vec<String> = env::args().collect();
241+
eprintln!("fix-python-soname: Arguments: {:?}", args);
230242

231243
if args.len() != 2 {
232244
return Err(format!("Usage: {} <path-to-node-file>", args[0]).into());
233245
}
234246

235247
let node_file_path = &args[1];
248+
eprintln!("fix-python-soname: Processing file: {}", node_file_path);
236249

237250
// Find the local Python library
238251
let new_python_lib = find_python_library()?;
239252

240253
// Read the file
254+
eprintln!("fix-python-soname: Reading ELF file...");
241255
let file_contents = fs::read(node_file_path)
242256
.map_err(|error| format!("Failed to read file: {error}"))?;
257+
eprintln!("fix-python-soname: ELF file size: {} bytes", file_contents.len());
243258

244259
// Parse the ELF file
260+
eprintln!("fix-python-soname: Parsing ELF file...");
245261
let mut elf = ElfContainer::parse(&file_contents)
246262
.map_err(|error| format!("Failed to parse ELF: {error}"))?;
247263

248264
// Get the list of needed libraries
265+
eprintln!("fix-python-soname: Getting needed libraries...");
249266
let needed_libs: Vec<String> = elf.inner.elf_needed()
250267
.map(|lib| String::from_utf8_lossy(lib).to_string())
251268
.collect();
252269

270+
eprintln!("fix-python-soname: Needed libraries: {:?}", needed_libs);
271+
253272
// Find the existing Python dependency
254273
let python_lib = needed_libs.iter()
255274
.find(|lib| lib.starts_with("libpython") && lib.contains(".so"))
256275
.ok_or("No Python library dependency found in the binary")?;
257276

258-
eprintln!("Current Python dependency: {python_lib}");
277+
eprintln!("fix-python-soname: Current Python dependency: {}", python_lib);
259278

260279
// Check if already pointing to the correct library
261280
if python_lib == &new_python_lib {
262-
eprintln!("Already using the correct Python library");
281+
eprintln!("fix-python-soname: Already using the correct Python library");
263282
return Ok(());
264283
}
265284

266-
eprintln!("Replacing with: {new_python_lib}");
285+
eprintln!("fix-python-soname: Replacing with: {}", new_python_lib);
267286

268287
// Create a map for replacement
269288
let mut replacements = HashMap::new();
270289
replacements.insert(python_lib.clone(), new_python_lib);
271290

272291
// Replace the needed dependency
292+
eprintln!("fix-python-soname: Replacing dependency...");
273293
elf.replace_needed(&replacements)
274294
.map_err(|error| format!("Failed to replace needed dependency: {error}"))?;
275295

276296
// Create backup
277297
let file_path = Path::new(node_file_path);
278298
let backup_path = file_path.with_extension("node.bak");
299+
eprintln!("fix-python-soname: Creating backup at: {}", backup_path.display());
279300
fs::copy(file_path, &backup_path)
280301
.map_err(|error| format!("Failed to create backup: {error}"))?;
281-
eprintln!("Created backup: {}", backup_path.display());
302+
eprintln!("fix-python-soname: Backup created successfully");
282303

283304
// Write the modified file
305+
eprintln!("fix-python-soname: Writing modified ELF file...");
284306
let output_file = File::create(node_file_path)
285307
.map_err(|error| format!("Failed to create output file: {error}"))?;
286308

287309
elf.write(&output_file)
288310
.map_err(|error| format!("Failed to write ELF: {error}"))?;
289311

290-
eprintln!("Successfully updated: {node_file_path}");
312+
eprintln!("fix-python-soname: Successfully updated: {}", node_file_path);
291313

292314
Ok(())
293315
}

0 commit comments

Comments
 (0)