Skip to content

Conversation

@georgewaters
Copy link

Addresses #760

Problem

When no request listeners are attached to the interceptor, handleRequest() goes through unnecessary async machinery before calling passthrough(). These async operations create microtask checkpoints that allow requests to pile up, and when they all passthrough simultaneously, socket contention causes EPIPE errors.

This particularly affects testcontainers usage where MSW is active but not intercepting Docker API requests.

Solution

Add an early return in handleRequest when no listeners are attached:

if (options.emitter.listenerCount('request') === 0) {
  return options.controller.passthrough()
}

Additionally, moved TLS property forwarding in MockHttpSocket to the secureConnect event to ensure mock TLS properties remain accessible until real values are available.

Reproduction

The issue is intermittent and timing-dependent, making it difficult to reproduce reliably in automated tests. A minimal reproduction is available at: https://github.com/georgewaters/testcontainers-kafka-msw-repro

Verification

Tested against Docker's Unix socket with 10 runs × 2000 concurrent requests each:

Scenario Without Fix With Fix
No listeners 70 errors total (0-12 per run) 0 errors
With listener calling passthrough() 0 errors 0 errors

* the mock TLS properties (set in constructor) to remain accessible
* until real values are available.
*/
if (Reflect.get(socket, 'encrypted')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context, socket.encrypted is guaranteed, right? We might as well skip this if entirely or make an invariant to ensure it instead.

this.emit('connect')
})
.on('secureConnect', () => this.emit('secureConnect'))
.on('secureConnect', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I remember it right that listening to the request.on('socket') won't give you any TLS properties until secureConnect is emitted on the socket? Is that the correct way to access those properties (like socket.encrypted)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockHttpSocket provides mock TLS values in its constructor so they're available immediately. For passthrough, mock values remain accessible until they're replaced with real values on secureConnect

Regarding the use of Reflect.get, my understanding is that it's been used to avoid TypeScript issues. createConnection() returns net.Socket (which doesn't have an encrypted), but at runtime this is actually a TLSSocket. We could potentially cast to TLSSocket here and access it as socket.encrypted? It's a slightly more type-safe approach


const HTTP_SOCKET_PATH = path.join(__dirname, './test-early-return.sock')

const httpServer = http.createServer((req, res) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have @open-draft/test-server installed already. Should use that one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is @open-draft/test-server compatible with UNIX sockets? I followed the pattern from http-unix-socket.test.ts here

const { res, text } = await waitForClientRequest(request)

expect(res.statusCode).toBe(200)
expect(await text()).toBe('ok')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer await expect(text()).resolves.toBe('ok'). Here's why.

Copy link
Member

@kettanaito kettanaito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @georgewaters. Thank you for working on this, looks like a great change. I left a few minor comments here and there, could you please take a look and let me know if something doesn't make sense?

@georgewaters
Copy link
Author

Hi, @georgewaters. Thank you for working on this, looks like a great change. I left a few minor comments here and there, could you please take a look and let me know if something doesn't make sense?

Hi @kettanaito, happy new year! Changes addressed and comments replied to. One that might need further discussion is around accessing the TLS properties on socket after secureConnect. I think the way it's being done is safe, but happy to hear your thoughts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants