Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/quic/application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,12 @@ class DefaultApplication final : public Session::Application {
// during the onstream callback (via MakeCallback re-entrancy).
return false;
}

// The stream was created, but was immediately destroyed because there's
// no onstream handler.
if (stream->is_destroyed()) [[unlikely]] {
return true;
}
} else {
stream = BaseObjectPtr<Stream>(Stream::From(stream_user_data));
if (!stream) {
Expand Down
4 changes: 2 additions & 2 deletions src/quic/session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -860,11 +860,11 @@ struct Session::Impl final : public MemoryRetainer {
}
}

endpoint->RemoveSession(config_.scid, remote_address_);

auto& binding = BindingData::Get(env());
if (stats_slot_) GetSessionStatsArena(binding).ReleaseSlot(stats_slot_);
if (state_slot_) GetSessionStateArena(binding).ReleaseSlot(state_slot_);

endpoint->RemoveSession(config_.scid, remote_address_);
}

void MemoryInfo(MemoryTracker* tracker) const override {
Expand Down
4 changes: 4 additions & 0 deletions src/quic/streams.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,10 @@ bool Stream::is_pending() const {
return state()->pending;
}

bool Stream::is_destroyed() const {
return stats()->destroyed_at != 0;
}

stream_id Stream::id() const {
return state()->id;
}
Expand Down
3 changes: 3 additions & 0 deletions src/quic/streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ class Stream final : public AsyncWrap,
// to be created.
bool is_pending() const;

// True if the stream is already destroyed.
bool is_destroyed() const;

// True if we've completely sent all outbound data for this stream.
// Importantly, this does not necessarily mean that we are completely
// done with the outbound data. We may still be waiting on outbound
Expand Down
47 changes: 47 additions & 0 deletions test/parallel/test-quic-stream-uni-no-onstream.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Flags: --experimental-quic --no-warnings
// Test: client-initiated unidirectional stream with no onstream
// The client creates a uni stream with no onstream.
// Verify that this causes no crash.

import { hasQuic, skip, mustCall } from '../common/index.mjs';
import { createPrivateKey } from 'node:crypto';
Comment thread
jasnell marked this conversation as resolved.
Outdated
import * as fixtures from '../common/fixtures.mjs';

const { readKey } = fixtures;

if (!hasQuic) {
skip('QUIC is not enabled');
}

const { listen, connect } = await import('../common/quic.mjs');

const serverKey = createPrivateKey(readKey('agent1-key.pem'));
const serverCert = readKey('agent1-cert.pem');

const done = Promise.withResolvers();

const serverEndpoint = await listen(mustCall(async (session) => {
await session.opened;
done.resolve();
}), {
sni: { '*': { keys: [serverKey], certs: [serverCert] } },
alpn: ['repro'],
});

const clientSession = await connect(serverEndpoint.address, {
servername: 'localhost',
alpn: 'repro',
ca: serverCert,
});

await clientSession.opened;

await clientSession.createUnidirectionalStream({
body: 'something something darkside',
});

await done.promise;

await clientSession.close();

await serverEndpoint.close();
Loading