@@ -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 )
0 commit comments