Skip to content

Commit d278b65

Browse files
authored
Expose internal insert function to pilot + optional ID during job insert (#1001)
Here, expose encapsulated core insert logic to the pilot so extensions can get access to it. We also add an optional ID to job insertion, a property that's not used most of the time but useful for fine-tine control over insertion that's required in some circumstances.
1 parent ef60d60 commit d278b65

25 files changed

Lines changed: 230 additions & 140 deletions

client.go

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import (
2323
"github.com/riverqueue/river/internal/notifier"
2424
"github.com/riverqueue/river/internal/notifylimiter"
2525
"github.com/riverqueue/river/internal/rivercommon"
26-
"github.com/riverqueue/river/internal/util/dbutil"
2726
"github.com/riverqueue/river/internal/workunit"
2827
"github.com/riverqueue/river/riverdriver"
2928
"github.com/riverqueue/river/rivershared/baseservice"
3029
"github.com/riverqueue/river/rivershared/riverpilot"
3130
"github.com/riverqueue/river/rivershared/startstop"
3231
"github.com/riverqueue/river/rivershared/testsignal"
32+
"github.com/riverqueue/river/rivershared/util/dbutil"
3333
"github.com/riverqueue/river/rivershared/util/maputil"
3434
"github.com/riverqueue/river/rivershared/util/sliceutil"
3535
"github.com/riverqueue/river/rivershared/util/testutil"
@@ -796,9 +796,11 @@ func NewClient[TTx any](driver riverdriver.Driver[TTx], config *Config) (*Client
796796
if client.pilot == nil {
797797
client.pilot = &riverpilot.StandardPilot{}
798798
}
799-
client.pilot.PilotInit(archetype, &riverpilot.PilotInitParams{
800-
WorkerMetadata: workerMetadata,
801-
})
799+
client.pilot.PilotInit(archetype, (&riverpilot.PilotInitParams{
800+
Insert: client.insertMany,
801+
NotifyNonTxJobInsert: client.notifyProducerWithoutListenerJobFetch,
802+
WorkerMetadata: workerMetadata,
803+
}).Validate())
802804
pluginPilot, _ := client.pilot.(pilotPlugin)
803805

804806
if withBaseService, ok := config.RetryPolicy.(baseservice.WithBaseService); ok {
@@ -898,12 +900,9 @@ func NewClient[TTx any](driver riverdriver.Driver[TTx], config *Config) (*Client
898900
{
899901
periodicJobEnqueuer, err := maintenance.NewPeriodicJobEnqueuer(archetype, &maintenance.PeriodicJobEnqueuerConfig{
900902
AdvisoryLockPrefix: config.AdvisoryLockPrefix,
901-
Insert: func(ctx context.Context, execTx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) error {
902-
_, err := client.insertMany(ctx, execTx, insertParams)
903-
return err
904-
},
905-
Pilot: client.pilot,
906-
Schema: config.Schema,
903+
Insert: client.insertMany,
904+
Pilot: client.pilot,
905+
Schema: config.Schema,
907906
}, driver.GetExecutor())
908907
if err != nil {
909908
return nil, err
@@ -1632,16 +1631,16 @@ func (c *Client[TTx]) Insert(ctx context.Context, args JobArgs, opts *InsertOpts
16321631
return nil, errNoDriverDBPool
16331632
}
16341633

1635-
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) (*insertManySharedResult, error) {
1634+
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) ([]*rivertype.JobInsertResult, error) {
16361635
return c.validateParamsAndInsertMany(ctx, execTx, []InsertManyParams{{Args: args, InsertOpts: opts}})
16371636
})
16381637
if err != nil {
16391638
return nil, err
16401639
}
16411640

1642-
c.notifyProducerWithoutListenerJobFetch(res.QueuesDeduped)
1641+
c.notifyProducerWithoutListenerJobFetch(ctx, res)
16431642

1644-
return res.InsertResults[0], nil
1643+
return res[0], nil
16451644
}
16461645

16471646
// InsertTx inserts a new job with the provided args on the given transaction.
@@ -1666,7 +1665,7 @@ func (c *Client[TTx]) InsertTx(ctx context.Context, tx TTx, args JobArgs, opts *
16661665
if err != nil {
16671666
return nil, err
16681667
}
1669-
return res.InsertResults[0], nil
1668+
return res[0], nil
16701669
}
16711670

16721671
// InsertManyParams encapsulates a single job combined with insert options for
@@ -1698,16 +1697,16 @@ func (c *Client[TTx]) InsertMany(ctx context.Context, params []InsertManyParams)
16981697
return nil, errNoDriverDBPool
16991698
}
17001699

1701-
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) (*insertManySharedResult, error) {
1700+
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) ([]*rivertype.JobInsertResult, error) {
17021701
return c.validateParamsAndInsertMany(ctx, execTx, params)
17031702
})
17041703
if err != nil {
17051704
return nil, err
17061705
}
17071706

1708-
c.notifyProducerWithoutListenerJobFetch(res.QueuesDeduped)
1707+
c.notifyProducerWithoutListenerJobFetch(ctx, res)
17091708

1710-
return res.InsertResults, nil
1709+
return res, nil
17111710
}
17121711

17131712
// InsertManyTx inserts many jobs at once. Each job is inserted as an
@@ -1733,14 +1732,14 @@ func (c *Client[TTx]) InsertManyTx(ctx context.Context, tx TTx, params []InsertM
17331732
if err != nil {
17341733
return nil, err
17351734
}
1736-
return res.InsertResults, nil
1735+
return res, nil
17371736
}
17381737

17391738
// validateParamsAndInsertMany is a helper method that wraps the insertMany
17401739
// method to provide param validation and conversion prior to calling the actual
17411740
// insertMany method. This allows insertMany to be reused by the
17421741
// PeriodicJobEnqueuer which cannot reference top-level river package types.
1743-
func (c *Client[TTx]) validateParamsAndInsertMany(ctx context.Context, execTx riverdriver.ExecutorTx, params []InsertManyParams) (*insertManySharedResult, error) {
1742+
func (c *Client[TTx]) validateParamsAndInsertMany(ctx context.Context, execTx riverdriver.ExecutorTx, params []InsertManyParams) ([]*rivertype.JobInsertResult, error) {
17441743
insertParams, err := c.insertManyParams(params)
17451744
if err != nil {
17461745
return nil, err
@@ -1751,7 +1750,7 @@ func (c *Client[TTx]) validateParamsAndInsertMany(ctx context.Context, execTx ri
17511750

17521751
// insertMany is a shared code path for InsertMany and InsertManyTx, also used
17531752
// by the PeriodicJobEnqueuer.
1754-
func (c *Client[TTx]) insertMany(ctx context.Context, execTx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) (*insertManySharedResult, error) {
1753+
func (c *Client[TTx]) insertMany(ctx context.Context, execTx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) ([]*rivertype.JobInsertResult, error) {
17551754
return c.insertManyShared(ctx, execTx, insertParams, func(ctx context.Context, insertParams []*riverdriver.JobInsertFastParams) ([]*rivertype.JobInsertResult, error) {
17561755
results, err := c.pilot.JobInsertMany(ctx, execTx, &riverdriver.JobInsertFastManyParams{
17571756
Jobs: insertParams,
@@ -1769,11 +1768,6 @@ func (c *Client[TTx]) insertMany(ctx context.Context, execTx riverdriver.Executo
17691768
})
17701769
}
17711770

1772-
type insertManySharedResult struct {
1773-
InsertResults []*rivertype.JobInsertResult
1774-
QueuesDeduped []string
1775-
}
1776-
17771771
// The shared code path for all Insert and InsertMany methods. It takes a
17781772
// function that executes the actual insert operation and allows for different
17791773
// implementations of the insert query to be passed in, each mapping their
@@ -1783,9 +1777,7 @@ func (c *Client[TTx]) insertManyShared(
17831777
tx riverdriver.ExecutorTx,
17841778
insertParams []*rivertype.JobInsertParams,
17851779
execute func(context.Context, []*riverdriver.JobInsertFastParams) ([]*rivertype.JobInsertResult, error),
1786-
) (*insertManySharedResult, error) {
1787-
var queuesDeduped []string
1788-
1780+
) ([]*rivertype.JobInsertResult, error) {
17891781
doInner := func(ctx context.Context) ([]*rivertype.JobInsertResult, error) {
17901782
for _, params := range insertParams {
17911783
for _, hook := range append(
@@ -1814,9 +1806,7 @@ func (c *Client[TTx]) insertManyShared(
18141806
}
18151807
}
18161808

1817-
queuesDeduped = sliceutil.Uniq(queues)
1818-
1819-
if err = c.maybeNotifyInsertForQueues(ctx, tx, queuesDeduped); err != nil {
1809+
if err = c.maybeNotifyInsertForQueues(ctx, tx, queues); err != nil {
18201810
return nil, err
18211811
}
18221812

@@ -1836,15 +1826,7 @@ func (c *Client[TTx]) insertManyShared(
18361826
}
18371827
}
18381828

1839-
insertResults, err := doInner(ctx)
1840-
if err != nil {
1841-
return nil, err
1842-
}
1843-
1844-
return &insertManySharedResult{
1845-
InsertResults: insertResults,
1846-
QueuesDeduped: queuesDeduped,
1847-
}, nil
1829+
return doInner(ctx)
18481830
}
18491831

18501832
// Validates input parameters for a batch insert operation and generates a set
@@ -1878,13 +1860,30 @@ func (c *Client[TTx]) insertManyParams(params []InsertManyParams) ([]*rivertype.
18781860
// Should only ever be invoked *outside* a transaction. If invoked within a
18791861
// transaction, the producer wouldn't yet be able to access the new jobs that
18801862
// triggered the notification because they're not committed yet.
1881-
func (c *Client[TTx]) notifyProducerWithoutListenerJobFetch(queuesDeduped []string) {
1863+
func (c *Client[TTx]) notifyProducerWithoutListenerJobFetch(_ context.Context, res []*rivertype.JobInsertResult) {
18821864
if c.driver.SupportsListener() || len(c.producersByQueueName) < 1 {
18831865
return
18841866
}
18851867

1886-
for _, queue := range queuesDeduped {
1887-
if producer, ok := c.producersByQueueName[queue]; ok {
1868+
// Special case for when we were handling exactly one job, which is a very
1869+
// common case. Acts as a minor optimization by avoiding the map allocation.
1870+
if len(res) == 1 {
1871+
if producer, ok := c.producersByQueueName[res[0].Job.Queue]; ok {
1872+
producer.TriggerJobFetch()
1873+
}
1874+
1875+
return
1876+
}
1877+
1878+
queuesTriggered := make(map[string]struct{})
1879+
1880+
for _, insertRes := range res {
1881+
if _, ok := queuesTriggered[insertRes.Job.Queue]; ok {
1882+
continue
1883+
}
1884+
queuesTriggered[insertRes.Job.Queue] = struct{}{}
1885+
1886+
if producer, ok := c.producersByQueueName[insertRes.Job.Queue]; ok {
18881887
producer.TriggerJobFetch()
18891888
}
18901889
}
@@ -1914,16 +1913,16 @@ func (c *Client[TTx]) InsertManyFast(ctx context.Context, params []InsertManyPar
19141913
}
19151914

19161915
// Wrap in a transaction in case we need to notify about inserts.
1917-
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) (*insertManySharedResult, error) {
1916+
res, err := dbutil.WithTxV(ctx, c.driver.GetExecutor(), func(ctx context.Context, execTx riverdriver.ExecutorTx) ([]*rivertype.JobInsertResult, error) {
19181917
return c.insertManyFast(ctx, execTx, params)
19191918
})
19201919
if err != nil {
19211920
return 0, err
19221921
}
19231922

1924-
c.notifyProducerWithoutListenerJobFetch(res.QueuesDeduped)
1923+
c.notifyProducerWithoutListenerJobFetch(ctx, res)
19251924

1926-
return len(res.InsertResults), nil
1925+
return len(res), nil
19271926
}
19281927

19291928
// InsertManyTx inserts many jobs at once using Postgres' `COPY FROM` mechanism,
@@ -1954,10 +1953,10 @@ func (c *Client[TTx]) InsertManyFastTx(ctx context.Context, tx TTx, params []Ins
19541953
if err != nil {
19551954
return 0, err
19561955
}
1957-
return len(res.InsertResults), nil
1956+
return len(res), nil
19581957
}
19591958

1960-
func (c *Client[TTx]) insertManyFast(ctx context.Context, execTx riverdriver.ExecutorTx, params []InsertManyParams) (*insertManySharedResult, error) {
1959+
func (c *Client[TTx]) insertManyFast(ctx context.Context, execTx riverdriver.ExecutorTx, params []InsertManyParams) ([]*rivertype.JobInsertResult, error) {
19611960
insertParams, err := c.insertManyParams(params)
19621961
if err != nil {
19631962
return nil, err
@@ -1978,12 +1977,13 @@ func (c *Client[TTx]) insertManyFast(ctx context.Context, execTx riverdriver.Exe
19781977
// Notify the given queues that new jobs are available. The queues list will be
19791978
// deduplicated and each will be checked to see if it is due for an insert
19801979
// notification from this client.
1981-
func (c *Client[TTx]) maybeNotifyInsertForQueues(ctx context.Context, tx riverdriver.ExecutorTx, queuesDeduped []string) error {
1982-
if len(queuesDeduped) < 1 {
1980+
func (c *Client[TTx]) maybeNotifyInsertForQueues(ctx context.Context, tx riverdriver.ExecutorTx, queues []string) error {
1981+
if len(queues) < 1 {
19831982
return nil
19841983
}
19851984

19861985
var (
1986+
queuesDeduped = sliceutil.Uniq(queues)
19871987
payloads = make([]string, 0, len(queuesDeduped))
19881988
queuesTriggered = make([]string, 0, len(queuesDeduped))
19891989
)

client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ import (
3030
"github.com/riverqueue/river/internal/rivercommon"
3131
"github.com/riverqueue/river/internal/riverinternaltest"
3232
"github.com/riverqueue/river/internal/riverinternaltest/retrypolicytest"
33-
"github.com/riverqueue/river/internal/util/dbutil"
3433
"github.com/riverqueue/river/riverdbtest"
3534
"github.com/riverqueue/river/riverdriver"
3635
"github.com/riverqueue/river/riverdriver/riverpgxv5"
3736
"github.com/riverqueue/river/rivershared/baseservice"
3837
"github.com/riverqueue/river/rivershared/riversharedtest"
3938
"github.com/riverqueue/river/rivershared/startstoptest"
4039
"github.com/riverqueue/river/rivershared/testfactory"
40+
"github.com/riverqueue/river/rivershared/util/dbutil"
4141
"github.com/riverqueue/river/rivershared/util/ptrutil"
4242
"github.com/riverqueue/river/rivershared/util/randutil"
4343
"github.com/riverqueue/river/rivershared/util/serviceutil"

internal/leadership/elector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import (
1111
"time"
1212

1313
"github.com/riverqueue/river/internal/notifier"
14-
"github.com/riverqueue/river/internal/util/dbutil"
1514
"github.com/riverqueue/river/riverdriver"
1615
"github.com/riverqueue/river/rivershared/baseservice"
1716
"github.com/riverqueue/river/rivershared/startstop"
1817
"github.com/riverqueue/river/rivershared/testsignal"
18+
"github.com/riverqueue/river/rivershared/util/dbutil"
1919
"github.com/riverqueue/river/rivershared/util/randutil"
2020
"github.com/riverqueue/river/rivershared/util/serviceutil"
2121
"github.com/riverqueue/river/rivershared/util/testutil"

internal/maintenance/job_scheduler.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ import (
1414
"github.com/riverqueue/river/rivershared/testsignal"
1515
"github.com/riverqueue/river/rivershared/util/randutil"
1616
"github.com/riverqueue/river/rivershared/util/serviceutil"
17-
"github.com/riverqueue/river/rivershared/util/sliceutil"
1817
"github.com/riverqueue/river/rivershared/util/testutil"
1918
"github.com/riverqueue/river/rivershared/util/timeutil"
20-
"github.com/riverqueue/river/rivertype"
2119
)
2220

2321
const (
@@ -36,11 +34,9 @@ func (ts *JobSchedulerTestSignals) Init(tb testutil.TestingTB) {
3634
ts.ScheduledBatch.Init(tb)
3735
}
3836

39-
type InsertFunc func(ctx context.Context, tx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) error
40-
4137
// NotifyInsert is a function to call to emit notifications for queues where
4238
// jobs were scheduled.
43-
type NotifyInsertFunc func(ctx context.Context, tx riverdriver.ExecutorTx, queuesDeduped []string) error
39+
type NotifyInsertFunc func(ctx context.Context, tx riverdriver.ExecutorTx, queues []string) error
4440

4541
type JobSchedulerConfig struct {
4642
// Interval is the amount of time between periodic checks for jobs to
@@ -187,7 +183,7 @@ func (s *JobScheduler) runOnce(ctx context.Context) (*schedulerRunOnceResult, er
187183
}
188184

189185
if len(queues) > 0 {
190-
if err := s.config.NotifyInsert(ctx, tx, sliceutil.Uniq(queues)); err != nil {
186+
if err := s.config.NotifyInsert(ctx, tx, queues); err != nil {
191187
return 0, fmt.Errorf("error notifying insert: %w", err)
192188
}
193189
s.TestSignals.NotifiedQueues.Signal(queues)

internal/maintenance/job_scheduler_test.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,8 @@ func TestJobScheduler(t *testing.T) {
320320

321321
scheduler, _ := setup(t, &testOpts{exec: exec, schema: schema})
322322
scheduler.config.Interval = time.Minute // should only trigger once for the initial run
323-
scheduler.config.NotifyInsert = func(ctx context.Context, tx riverdriver.ExecutorTx, queuesDeduped []string) error {
324-
notifyCh <- queuesDeduped
323+
scheduler.config.NotifyInsert = func(ctx context.Context, tx riverdriver.ExecutorTx, queues []string) error {
324+
notifyCh <- queues
325325
return nil
326326
}
327327
now := time.Now().UTC()
@@ -362,10 +362,8 @@ func TestJobScheduler(t *testing.T) {
362362
require.NoError(t, scheduler.Start(ctx))
363363
scheduler.TestSignals.ScheduledBatch.WaitOrTimeout()
364364

365-
expectedQueues := []string{"queue1", "queue2", "queue3", "queue4"}
366-
367-
notifiedQueuesDeduped := riversharedtest.WaitOrTimeout(t, notifyCh)
368-
sort.Strings(notifiedQueuesDeduped)
369-
require.Equal(t, expectedQueues, notifiedQueuesDeduped)
365+
notifiedQueues := riversharedtest.WaitOrTimeout(t, notifyCh)
366+
sort.Strings(notifiedQueues)
367+
require.Equal(t, []string{"queue1", "queue2", "queue2", "queue3", "queue4"}, notifiedQueues)
370368
})
371369
}

internal/maintenance/periodic_job_enqueuer.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func (j *PeriodicJob) validate() error {
8282
return nil
8383
}
8484

85+
type InsertFunc func(ctx context.Context, tx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) ([]*rivertype.JobInsertResult, error)
86+
8587
type PeriodicJobEnqueuerConfig struct {
8688
AdvisoryLockPrefix int32
8789

@@ -450,7 +452,7 @@ func (s *PeriodicJobEnqueuer) insertBatch(ctx context.Context, insertParamsMany
450452
defer tx.Rollback(ctx)
451453

452454
if len(insertParamsMany) > 0 {
453-
if err := s.Config.Insert(ctx, tx, insertParamsMany); err != nil {
455+
if _, err := s.Config.Insert(ctx, tx, insertParamsMany); err != nil {
454456
s.Logger.ErrorContext(ctx, s.Name+": Error inserting periodic jobs",
455457
"error", err.Error(), "num_jobs", len(insertParamsMany))
456458
}

internal/maintenance/periodic_job_enqueuer_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,15 @@ func TestPeriodicJobEnqueuer(t *testing.T) {
132132

133133
// A simplified version of `Client.insertMany` that only inserts jobs directly
134134
// via the driver instead of using the pilot.
135-
makeInsertFunc := func(schema string) func(ctx context.Context, tx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) error {
136-
return func(ctx context.Context, tx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) error {
135+
makeInsertFunc := func(schema string) func(ctx context.Context, execTx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) ([]*rivertype.JobInsertResult, error) {
136+
return func(ctx context.Context, tx riverdriver.ExecutorTx, insertParams []*rivertype.JobInsertParams) ([]*rivertype.JobInsertResult, error) {
137137
_, err := tx.JobInsertFastMany(ctx, &riverdriver.JobInsertFastManyParams{
138138
Jobs: sliceutil.Map(insertParams, func(params *rivertype.JobInsertParams) *riverdriver.JobInsertFastParams {
139139
return (*riverdriver.JobInsertFastParams)(params)
140140
}),
141141
Schema: schema,
142142
})
143-
return err
143+
return nil, err
144144
}
145145
}
146146

riverdriver/river_driver_interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ type JobGetStuckParams struct {
424424
}
425425

426426
type JobInsertFastParams struct {
427+
ID *int64
427428
// Args contains the raw underlying job arguments struct. It has already been
428429
// encoded into EncodedArgs, but the original is kept here for to leverage its
429430
// struct tags and interfaces, such as for use in unique key generation.

0 commit comments

Comments
 (0)