#Mutation error: `Returned promise will never resolve` caused by triggers

53 messages · Page 1 of 1 (latest)

timid fjord
#

I've never seen this error before, and when i get rid of this trigger in my blocks mutation it goes away...

The original mutation contains some promise all statements that insert docs into the blocks table

Any ideas?

triggers.register("blocks", async (ctx, change) => {
  if (change.operation === "insert") {
    const newBlockFid = parseFid(change.newDoc.fid, "blocks");
    // Try to fetch block with same fid - which will throw if there's multiple. Note that at the time of running this query the new doc is returned by the query
    await _getBlockByFid(ctx, {
      blockFid: newBlockFid,
    });
  } else if (change.operation === "update") {
    // Throw an error if the fid is being updated
    if (change.oldDoc.fid !== change.newDoc.fid) {
      throw new ConvexError("Block FID cannot be updated");
    }
  }
});
lost ironBOT
#

Thanks for posting in #1088161997662724167.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.

    - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
    - Use [search.convex.dev](https://search.convex.dev) to search Docs, Stack, and Discord all at once.
    - Additionally, you can post your questions in the Convex Community's #1228095053885476985 channel to receive a response from AI.
    - Avoid tagging staff unless specifically instructed.

    Thank you!
timid fjord
#

also it took me a very long time to find that the issue was related to the triggers, so a better error log would be very helpful in the future

dusky smelt
#

Hmm. This error indicates that there's a deadlock (which is why it's so opaque; as far as Convex knows, the function just never returned). It's probably caused by the lock Triggers uses to guard against parallel updates, but I don't see how it could happen. Can you share more of the code so I can try to repro?

timid fjord
#

sent you some more details through a dm!

#

btw is there a way to see the sync status of triggers in the dashboard? similar to regular functions

dusky smelt
timid fjord
#

Ah sorry i mean seeing the source code that has been deployed for triggers

timid fjord
# dusky smelt Hmm. This error indicates that there's a deadlock (which is why it's so opaque; ...

Getting the same error here:

export const queueEdits = authenticatedMutation({
  args: {
    edits: v.array(vFid("firestoreEdits")),
  },
  handler: async (ctx, args) => {
    const uniqueEdits = [...new Set(args.edits)];

    // Filter so that we only queue edits that are in "uncommitted" or "error" status
    const editsToQueue = await Promise.all(
      uniqueEdits.map((editId) =>
        _getFirestoreEditByFid(ctx, { editFid: editId })
      )
    ).then((edits) =>
      edits.filter(
        (edit) => edit.status === "uncommitted" || edit.status === "error"
      )
    );

    return await _updateEditsStatus(ctx, {
      edits: editsToQueue,
      status: "queued",
    });
  },
});
export const _updateEditsStatus = internalMutation({
  args: {
    edits: v.array(doc(fireviewSchema, "firestoreEdits")),
    status: zodToConvex(zFirestoreDataChangeStatus),
  },
  handler: async (ctx, args) => {
     // Chunk the edits to avoid overwhelming the system
     const chunks = chunk(args.edits, 50);

     for (const editChunk of chunks) {
       await Promise.all(
         editChunk.map(async (edit) => {
           await ctx.db.patch(edit._id, {
             status: args.status,
           });
         })
       );
     }
  },
});

The only trigger for this table is essentially the same as what I DM'd you before for the other tables

#

i should say both authenticatedMutation and internalMutation are wrapped by triggers

timid fjord
#

The only way for us to use aggregates robustly is in conjunction with triggers but this is blocking that so it would be great to understand how to fix this

primal ferry
#

I have the same error. The "funny" part is that even if I edit the code of the trigger to do nothing (no query nor mutations), I get this error:

// testTrigger.ts
import {triggers} from './shared/triggers';

triggers.register('users', async (ctx, change) => {
// Do nothing
});
// function.ts
import {
  mutation as rawMutation,
  internalMutation as rawInternalMutation,
} from '../_generated/server';
import {customCtx, customMutation} from 'convex-helpers/server/customFunctions';
import {triggers} from './triggers';
import '../derivedUserFriends';
import '../testTrigger'; // THIS IMPORT CAUSES THE AFOREMENTIONED ERROR

export const mutation = customMutation(rawMutation, customCtx(triggers.wrapDB));
export const internalMutation = customMutation(
  rawInternalMutation,
  customCtx(triggers.wrapDB),
);
#

Given I have another trigger that works and this trigger does nothing, I suspect this is something related to the mutation that happens within the 'users' table that is the issue (but it works well without the trigger)

dusky smelt
#

Is there a chance one of you could publish a small repro to github / pastebin? This definitely sounds like a triggers bug, but I'm kind of stumped unless I can reproduce it myself

dusky smelt
#

so far the only way i've found to repro the error is something like this (wrapping it twice)

export const mutation1 = customMutation(rawMutation, customCtx(triggers.wrapDB));
export const internalMutation1 = customMutation(
  rawInternalMutation,
  customCtx(triggers.wrapDB),
);

export const mutation = customMutation(mutation1, customCtx(triggers.wrapDB));
export const internalMutation = customMutation(
  internalMutation1,
  customCtx(triggers.wrapDB),
);
timid fjord
#

ah right, i have wrapped both authenticatedMutation and internalMutation with triggers, and i think the issue occurs when i call an internal mutation from an auth one

#

how do i fix this best?

dusky smelt
#

how are you calling the internal mutation from the auth one? with ctx.runMutation?

timid fjord
#

no just await function_name

dusky smelt
#

Calling mutations directly as functions is not supported, and custom functions is one of the reasons why

#

The two options are to refactor out the handler (as described in best practices) or to call ctx.runMutation, which has extra overhead

timid fjord
#

this is not the case with calling internal queries directly right?

timid fjord
#

Calling mutations directly as functions is not supported
is there a good way to catch this during development? idk if this is possible with eslint rules...

dusky smelt
dusky smelt
# timid fjord > Calling mutations directly as functions is not supported is there a good way t...

Eslint would be great, but I'm not sure how such a rule would work. We're looking into it. For triggers we can throw a better error https://github.com/get-convex/convex-helpers/pull/348 which would have caught it in your case.

GitHub

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

timid fjord
#

nice that's already very helpful, but for queries i've never run into any issues so it's hard to detect

timid fjord
#

and how can i quantify the overhead of ctx.runMutation / ctx.runQuery? is it mostly due to arg validation?

dusky smelt
#

the issue is calling any convex function directly from another convex function.

ctx.runMutation / ctx.runQuery start up a new javascript environment, serde the args and return values, and do arg validation. We don't recommend using them unless you're writing a component. The real recommendation is https://docs.convex.dev/production/best-practices/#use-helper-functions-to-write-shared-code

Here's a collection of our recommendations on how best to use Convex to build

#

are there any docs that indicated calling a convex function directly as if it were a function would work? We're trying to track down such docs.

timid fjord
#

TS errors would definitely help instantly break the habit

dusky smelt
#

absolutely, i'm trying those out now 👍

timid fjord
#

in the docs

dusky smelt
#

true, good call out

timid fjord
#

what about calling convex functions from triggers? I assume that's not any different

strange kettle
# timid fjord > For actions, sharing code via helper functions instead of using ctx.runAction ...

For a while I've been a little foggy on what such "helper functions" might look like, but I think I saw an example in a video posted to the Convex YT channel last night. Jamie walks through the recreation of a Pokemon-related app that was demonstrated on Theo's channel. Here's a link to the relevant part of the video where he mentions this helper (updateTally).

Having an example like this in the docs would be really helpful IMO.

In a recent video (https://www.youtube.com/watch?v=O-EWIlZW0mM), Theo Browne built a "rate the roundest Pokemon" app using five different stacks. In a shocking turn of events, Convex was not one of the five.

In this video, Convex co-founder Jamie Turner walks through what it took to port the tRPC version to Convex. Spoiler: not much. And we end...

▶ Play video
dusky smelt
#

The docs have example helper functions ensureTeamAdmin and getCurrentUser. How can we improve the example?

strange kettle
#

I had to go digging to find where those examples were shown. Ironically they're right under the text that @timid fjord quoted, which comes from the "Best Practices" page under "Production". 🫢

Admittedly I don't recall reading that page yet (I thought I had, but those functions don't look familiar), and I'm trying to figure out why.

First off, I guess I've seen the phrase "helper functions" enough that I thought that the text David quoted was from some other part of the docs that I had read.

That aside, I'm guessing that I missed it because my approach to docs is to read the first few pages/sections to get the core info that I need, then to refer to the rest on an as-needed basis; e.g. to brush up on specific features. For me, I'd love to see the whole "Best Practices" page surfaced higher in the docs hierarchy. It feels buried in its current location, plus I feel that a list of best practices is useful for all phases of development, not just production. If I'd seen those example functions earlier in my introduction to Convex, it would have been really helpful as I was mulling over how to solve certain problems.

dusky smelt
#

makes sense. i think we're planning on reorganizing docs like this. (i also linked that section twice in this thread 😛 )

#

Jamie's video is also a good example though. i watched it last night 😄

safe star
#

I also had a postmortem session with @slow trellis where I learned a lot of tricks about presenting code more clearly. I'm pretty excited for the next video, which I think is going to be excellent

#

slowly figuring things out!

safe star
#

with updateTally being said helper function

strange kettle
timid fjord
dusky smelt
timid fjord
#

Is there a way we could somehow opt into it then?

dusky smelt
#

Not that i can think of -- unless you want a custom build of the convex package