7
7
"fmt"
8
8
"strconv"
9
9
"strings"
10
+ "sync"
10
11
"time"
11
12
12
13
"github.com/google/uuid"
@@ -68,20 +69,82 @@ type Config struct {
68
69
69
70
var _ odoo.OdooStorage = & Odoo16Storage {}
70
71
72
+ // NewOdoo16Storage returns a new storage provider for BillingEntities
73
+ // The storage provider uses Odoo 16 as the backend.
71
74
func NewOdoo16Storage (credentials OdooCredentials , conf Config ) * Odoo16Storage {
72
75
return & Odoo16Storage {
73
76
config : conf ,
74
- sessionCreator : func (ctx context.Context ) (Odoo16Client , error ) {
75
- return odooclient .NewClient (& credentials )
76
- },
77
+ sessionCreator : CachingClientCreator (func (ctx context.Context ) (Odoo16Client , error ) {
78
+ c , err := odooclient .NewClient (& credentials )
79
+ return & OdooClientWithFullInitialization {c }, err
80
+ }),
77
81
}
78
82
}
79
83
80
84
func NewFailedRecordScrubber (credentials OdooCredentials ) * FailedRecordScrubber {
81
85
return & FailedRecordScrubber {
82
- sessionCreator : func (ctx context.Context ) (Odoo16Client , error ) {
83
- return odooclient .NewClient (& credentials )
84
- },
86
+ sessionCreator : CachingClientCreator (func (ctx context.Context ) (Odoo16Client , error ) {
87
+ c , err := odooclient .NewClient (& credentials )
88
+ return & OdooClientWithFullInitialization {c }, err
89
+ }),
90
+ }
91
+ }
92
+
93
+ // OdooClientWithFullInitialization is a wrapper around the Odoo client that provides a FullInitialization method.
94
+ type OdooClientWithFullInitialization struct {
95
+ * odooclient.Client
96
+ }
97
+
98
+ // FullInitialization is a workaround for the odooclient initialization which is not thread-safe.
99
+ // This function performs a full initialization of the client by calling execute_kw which initializes the "object" client.
100
+ // https://github.com/appuio/go-odoo/blob/a2a337fdf12becaeaa920ee8093402d6d01144f3/odoo.go#L337
101
+ // After this call the client should be thread-safe and ready to use.
102
+ // The "common" client is already initialized in the NewClient function through the call of "authenticate".
103
+ // https://github.com/appuio/go-odoo/blob/a2a337fdf12becaeaa920ee8093402d6d01144f3/odoo.go#L53
104
+ // https://github.com/appuio/go-odoo/blob/a2a337fdf12becaeaa920ee8093402d6d01144f3/odoo.go#L346
105
+ func (c * OdooClientWithFullInitialization ) FullInitialization () error {
106
+ _ , err := c .Client .ExecuteKw ("search_count" , odooclient .ResPartnerModel , []any {* odooclient .NewCriteria ()}, nil )
107
+ return err
108
+ }
109
+
110
+ // CachingClientCreator accepts and returns a function that creates a new Odoo16Client instance.
111
+ // The function caches the client instance returned from the upstream client creation function and returns it on subsequent calls.
112
+ // If an error occurs during the creation of the client, the error is returned and the client creation is retried on the next call.
113
+ // No client is cached if an error occurs during the creation.
114
+ //
115
+ // This function is useful to avoid creating a new client instance for every request.
116
+ // Odoo clients track connections and session state internally, so reusing a client instance is beneficial.
117
+ // The upstream Odoo client is not thread-safe until a full initialization is performed.
118
+ // See the FullInitialization method on OdooClientWithFullInitialization for a workaround.
119
+ func CachingClientCreator (sc func (context.Context ) (Odoo16Client , error )) func (context.Context ) (Odoo16Client , error ) {
120
+ clientMux := sync.RWMutex {}
121
+ var client Odoo16Client
122
+
123
+ return func (ctx context.Context ) (Odoo16Client , error ) {
124
+ clientMux .RLock ()
125
+ if client != nil {
126
+ clientMux .RUnlock ()
127
+ return client , nil
128
+ }
129
+ clientMux .RUnlock ()
130
+
131
+ clientMux .Lock ()
132
+ defer clientMux .Unlock ()
133
+
134
+ // recheck if client was created between RUnlock and Lock
135
+ if client != nil {
136
+ return client , nil
137
+ }
138
+
139
+ c , err := sc (ctx )
140
+ if err != nil {
141
+ return nil , err
142
+ }
143
+ if err := c .FullInitialization (); err != nil {
144
+ return nil , fmt .Errorf ("error during full initialization: %w" , err )
145
+ }
146
+ client = c
147
+ return client , nil
85
148
}
86
149
}
87
150
@@ -97,6 +160,10 @@ type FailedRecordScrubber struct {
97
160
98
161
//go:generate go run go.uber.org/mock/mockgen -destination=./odoo16mock/$GOFILE -package odoo16mock . Odoo16Client
99
162
type Odoo16Client interface {
163
+ // FullInitialization performs a full initialization of the client.
164
+ // After this call the client must be thread-safe and ready to use.
165
+ FullInitialization () error
166
+
100
167
Update (string , []int64 , interface {}) error
101
168
FindResPartners (* odooclient.Criteria , * odooclient.Options ) (* odooclient.ResPartners , error )
102
169
CreateResPartner (* odooclient.ResPartner ) (int64 , error )
0 commit comments