diff --git a/example/golang/main.go b/example/golang/main.go index ffcb6b6..3495ec1 100644 --- a/example/golang/main.go +++ b/example/golang/main.go @@ -47,7 +47,7 @@ func main() { cmd := exec.Command("node", path.Join(cwd, "ts/dist/index.js")) - ipc, err := kittenipc.NewParent(cmd, &localApi) + ipc, err := kittenipc.NewParent(cmd, nil, &localApi) if err != nil { log.Panic(err) } diff --git a/example/ts/src/index.ts b/example/ts/src/index.ts index 055b2aa..c92deeb 100644 --- a/example/ts/src/index.ts +++ b/example/ts/src/index.ts @@ -28,7 +28,7 @@ class TsIpcApi { async function main() { const localApi = new TsIpcApi(); - const ipc = new ChildIPC(localApi); + const ipc = new ChildIPC(undefined, localApi); const remoteApi = new GoIpcApi(ipc); await ipc.start(); diff --git a/lib/golang/child.go b/lib/golang/child.go index bfbb878..0f52af0 100644 --- a/lib/golang/child.go +++ b/lib/golang/child.go @@ -11,13 +11,17 @@ type ChildIPC struct { *ipcCommon } -func NewChild(localApis ...any) (*ChildIPC, error) { +func NewChild(opts *Options, localApis ...any) (*ChildIPC, error) { + if opts == nil { + opts = &Options{} + } c := ChildIPC{ ipcCommon: &ipcCommon{ - localApis: mapTypeNames(localApis), - pendingCalls: make(map[int64]*pendingCall), - errCh: make(chan error, 1), - ctx: context.Background(), + localApis: mapTypeNames(localApis), + pendingCalls: make(map[int64]*pendingCall), + errCh: make(chan error, 1), + ctx: context.Background(), + debugMessages: opts.DebugMessages, }, } diff --git a/lib/golang/common.go b/lib/golang/common.go index 6254dbf..25e4c52 100644 --- a/lib/golang/common.go +++ b/lib/golang/common.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net" "reflect" "strings" @@ -26,6 +27,10 @@ type pendingCall struct { resultChan chan callResult } +type Options struct { + DebugMessages bool +} + type ipcCommon struct { localApis map[string]any socketPath string @@ -38,6 +43,7 @@ type ipcCommon struct { mu sync.Mutex writeMu sync.Mutex ctx context.Context + debugMessages bool } func (ipc *ipcCommon) readConn() { @@ -46,6 +52,9 @@ func (ipc *ipcCommon) readConn() { for scn.Scan() { var msg Message msgBytes := scn.Bytes() + if ipc.debugMessages { + log.Printf("[ipc recv] %s", string(msgBytes)) + } if err := json.Unmarshal(msgBytes, &msg); err != nil { ipc.raiseErr(fmt.Errorf("unmarshal message: %w", err)) break @@ -71,6 +80,9 @@ func (ipc *ipcCommon) sendMsg(msg Message) error { if err != nil { return fmt.Errorf("marshal message: %w", err) } + if ipc.debugMessages { + log.Printf("[ipc send] %s", string(data)) + } data = append(data, '\n') ipc.writeMu.Lock() diff --git a/lib/golang/lib_test.go b/lib/golang/lib_test.go index 2e8f877..9d977ff 100644 --- a/lib/golang/lib_test.go +++ b/lib/golang/lib_test.go @@ -11,27 +11,27 @@ import ( func TestNewParent(t *testing.T) { t.Run("socket argument in command", func(t *testing.T) { cmd := exec.Command("/bin/sh", ipcSocketArg, "/tmp/kek") - _, err := NewParent(cmd) + _, err := NewParent(cmd, nil) assert.Error(t, err) }) t.Run("nonexistent binary", func(t *testing.T) { cmd := exec.Command("/nonexistent/binary") - p, err := NewParent(cmd) + p, err := NewParent(cmd, nil) assert.NoError(t, err) assert.Error(t, p.Start()) }) t.Run("connection timeout", func(t *testing.T) { cmd := exec.Command("../testdata/sleep15.sh") - p, err := NewParent(cmd) + p, err := NewParent(cmd, nil) assert.NoError(t, err) assert.Error(t, p.Start()) }) t.Run("child finished before accepting connection", func(t *testing.T) { cmd := exec.Command("../testdata/sleep3.sh") - p, err := NewParent(cmd) + p, err := NewParent(cmd, nil) assert.NoError(t, err) start := time.Now() assert.Error(t, p.Start()) diff --git a/lib/golang/parent.go b/lib/golang/parent.go index 0e84147..4f54bea 100644 --- a/lib/golang/parent.go +++ b/lib/golang/parent.go @@ -22,18 +22,22 @@ type ParentIPC struct { cmdErr error } -func NewParent(cmd *exec.Cmd, localApis ...any) (*ParentIPC, error) { - return NewParentWithContext(context.Background(), cmd, localApis...) +func NewParent(cmd *exec.Cmd, opts *Options, localApis ...any) (*ParentIPC, error) { + return NewParentWithContext(context.Background(), cmd, opts, localApis...) } -func NewParentWithContext(ctx context.Context, cmd *exec.Cmd, localApis ...any) (*ParentIPC, error) { +func NewParentWithContext(ctx context.Context, cmd *exec.Cmd, opts *Options, localApis ...any) (*ParentIPC, error) { + if opts == nil { + opts = &Options{} + } p := ParentIPC{ ipcCommon: &ipcCommon{ - localApis: mapTypeNames(localApis), - pendingCalls: make(map[int64]*pendingCall), - errCh: make(chan error, 1), - socketPath: filepath.Join(os.TempDir(), fmt.Sprintf("kitten-ipc-%d-%d.sock", os.Getpid(), rand.Int63())), - ctx: ctx, + localApis: mapTypeNames(localApis), + pendingCalls: make(map[int64]*pendingCall), + errCh: make(chan error, 1), + socketPath: filepath.Join(os.TempDir(), fmt.Sprintf("kitten-ipc-%d-%d.sock", os.Getpid(), rand.Int63())), + ctx: ctx, + debugMessages: opts.DebugMessages, }, cmd: cmd, } diff --git a/lib/ts/src/child.ts b/lib/ts/src/child.ts index c0bcef3..218d431 100644 --- a/lib/ts/src/child.ts +++ b/lib/ts/src/child.ts @@ -1,10 +1,10 @@ import * as net from 'node:net'; -import {IPCCommon} from './common.js'; +import {IPCCommon, type IPCOptions} from './common.js'; import {socketPathFromArgs} from './util.js'; export class ChildIPC extends IPCCommon { - constructor(...localApis: object[]) { - super(localApis, socketPathFromArgs()); + constructor(opts?: IPCOptions, ...localApis: object[]) { + super(localApis, socketPathFromArgs(), opts); } async start(): Promise { diff --git a/lib/ts/src/common.ts b/lib/ts/src/common.ts index f82d413..8741ab2 100644 --- a/lib/ts/src/common.ts +++ b/lib/ts/src/common.ts @@ -4,6 +4,10 @@ import {AsyncQueue} from './asyncqueue.js'; import type {CallMessage, CallResult, Message, ResponseMessage, Vals} from './protocol.js'; import {MsgType} from './protocol.js'; +export interface IPCOptions { + debugMessages?: boolean; +} + export abstract class IPCCommon { protected localApis: Record; protected socketPath: string; @@ -13,12 +17,14 @@ export abstract class IPCCommon { protected stopRequested: boolean = false; protected processingCalls: number = 0; protected ready = false; + protected debugMessages: boolean; protected errorQueue = new AsyncQueue(); protected onClose?: () => void; - protected constructor(localApis: object[], socketPath: string) { + protected constructor(localApis: object[], socketPath: string, opts?: IPCOptions) { this.socketPath = socketPath; + this.debugMessages = opts?.debugMessages ?? false; this.localApis = {}; for (const localApi of localApis) { @@ -47,6 +53,9 @@ export abstract class IPCCommon { rl.on('line', (line) => { try { + if (this.debugMessages) { + console.log(`[ipc recv] ${line}`); + } const msg: Message = JSON.parse(line); this.processMsg(msg); } catch (e) { @@ -73,6 +82,9 @@ export abstract class IPCCommon { try { const data = JSON.stringify(msg) + '\n'; + if (this.debugMessages) { + console.log(`[ipc send] ${JSON.stringify(msg)}`); + } this.conn.write(data); } catch (e) { this.raiseErr(new Error(`send response for ${ msg.id }: ${ e }`)); diff --git a/lib/ts/src/index.ts b/lib/ts/src/index.ts index 8055b34..1eb2960 100644 --- a/lib/ts/src/index.ts +++ b/lib/ts/src/index.ts @@ -1,2 +1,3 @@ export {ParentIPC} from './parent.js'; export {ChildIPC} from './child.js'; +export type {IPCOptions} from './common.js'; diff --git a/lib/ts/src/parent.ts b/lib/ts/src/parent.ts index 5edd540..a5698f0 100644 --- a/lib/ts/src/parent.ts +++ b/lib/ts/src/parent.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; import * as fs from 'node:fs'; import * as crypto from 'node:crypto'; import {type ChildProcess, spawn} from 'node:child_process'; -import {IPCCommon} from './common.js'; +import {IPCCommon, type IPCOptions} from './common.js'; import {timeout} from './util.js'; const IPC_SOCKET_ARG = 'ipc-socket'; @@ -18,9 +18,9 @@ export class ParentIPC extends IPCCommon { private cmdExitResult: { code: number | null, signal: string | null } | null = null; private cmdExitCallbacks: ((result: { code: number | null, signal: string | null }) => void)[] = []; - constructor(cmdPath: string, cmdArgs: string[], ...localApis: object[]) { + constructor(cmdPath: string, cmdArgs: string[], opts?: IPCOptions, ...localApis: object[]) { const socketPath = path.join(os.tmpdir(), `kitten-ipc-${ process.pid }-${ crypto.randomInt(2**48 - 1) }.sock`); - super(localApis, socketPath); + super(localApis, socketPath, opts); this.cmdPath = cmdPath; if (cmdArgs.includes(`--${ IPC_SOCKET_ARG }`)) {