Duplicate subscribe/complete operations, a possible bug? #422
-
Hi, @enisdenjo I'm writing this as a discussion as i'm not sure if this is a websocket bug ([email protected]) or [email protected] subscription bug, but in the following scenario where the client just sends double subscribe the server should return 4409 however it does spin happily two subscriptions on the same client since frames on the websocket can be sent simultaneously (code snippet below), then observe in Chrome or Firefox two websocket operations subscribe spin at the exact same millisecond. ` this.reloadGraphiql= () => {
` |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 12 replies
-
Hi there, thanks for writing! Are you using graphql-ws also on the backend? Do the subscriptions have the same ID? Can you share the WS frames/messages from the DevTools? |
Beta Was this translation helpful? Give feedback.
-
I checked the bad client you attached with #422 (reply in thread). The reason why you have 2 subscriptions issued is because you actually do 2 subscribes... Just remove the second subscribe and there wont be any double subscription requests. function subscribe(payload) {
let deferred = null;
const pending = [];
let throwMe = null,
done = false;
dispose = wsClient.subscribe(payload, {
next: (data) => {
pending.push(data);
deferred?.resolve(false);
},
error: (err) => {
if (err instanceof Error) {
throwMe = err;
} else if (err instanceof CloseEvent) {
throwMe = new Error(
`Socket closed with event ${err.code} ${
err.reason || ""
}`.trim()
);
} else {
// GraphQLError[]
throwMe = new Error(err.map(({ message }) => message).join(", "));
}
deferred?.reject({
message: throwMe.message
});
},
complete: () => {
done = true;
if (customError && customError.message) {
deferred?.reject(customError);
} else {
deferred?.resolve(true);
}
},
});
- // The next block sends double subscribe
- dispose = wsClient.subscribe(payload, {
- next: (data) => {
- pending.push(data);
- deferred?.resolve(false);
- },
- error: (err) => {
- if (err instanceof Error) {
- throwMe = err;
- } else if (err instanceof CloseEvent) {
- throwMe = new Error(
- `Socket closed with event ${err.code} ${
- err.reason || ""
- }`.trim()
- );
- } else {
- // GraphQLError[]
- throwMe = new Error(err.map(({ message }) => message).join(", "));
- }
- deferred?.reject({
- message: throwMe.message
- });
- },
- complete: () => {
- done = true;
- if (customError && customError.message) {
- deferred?.reject(customError);
- } else {
- deferred?.resolve(true);
- }
- },
- });
- // ------------------end of double subscribe-------------------- //
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (done) return { done: true, value: undefined };
if (throwMe) throw throwMe;
if (pending.length) return { value: pending.shift() };
return (await new Promise(
(resolve, reject) => (deferred = { resolve, reject })
))
? { done: true, value: undefined }
: { value: pending.shift() };
},
async return() {
dispose();
return { done: true, value: undefined };
},
};
} |
Beta Was this translation helpful? Give feedback.
Hi, @enisdenjo
Sorry didnt have time to write one, but here is another example query followed by subscription:
The lock could only help to serialize it but unfairly and creates more problems including deadlock. The way i solve this problem is the following, since clearly the query type subscribe sends complete and the subscription ones request the client to send complete, effectively makes each connection calls init -> subscribe -> complete, the client ids cannot be trusted, the protocol says regardless of the type but not regardless of operation AST. Which should it execute query or subscription when they work differently? Looks like apollo 3 executes both but returns to client only the…