@@ -2,15 +2,15 @@ defmodule Mongo.Session do
2
2
3
3
@ moduledoc """
4
4
5
- see https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst#committransaction
6
-
7
-
8
- see https://andrealeopardi.com/posts/connection-managers-with-gen_statem/
5
+ For gen_statem look here
6
+ * see https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst#committransaction
7
+ * see https://andrealeopardi.com/posts/connection-managers-with-gen_statem/
9
8
"""
10
9
11
10
@ behaviour :gen_statem
12
11
13
12
import Keywords
13
+ import Mongo.WriteConcern
14
14
15
15
alias Mongo.Session.ServerSession
16
16
alias Mongo.Session
@@ -114,6 +114,39 @@ defmodule Mongo.Session do
114
114
end
115
115
end
116
116
117
+ def with_transaction ( topology_pid , fun , opts \\ [ ] ) do
118
+
119
+ with { :ok , session } <- Session . start_session ( topology_pid , :write , opts ) ,
120
+ :ok <- Session . start_transaction ( session ) do
121
+
122
+ with { :ok , result } <- run_function ( fun , Keyword . merge ( opts , session: session ) ) do
123
+ commit_transaction ( session )
124
+ end_session ( topology_pid , session )
125
+ { :ok , result }
126
+ else
127
+ error ->
128
+ abort_transaction ( session )
129
+ end_session ( topology_pid , session )
130
+ ## todo rerun
131
+ error
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+
139
+ defp run_function ( fun , opts ) do
140
+
141
+ ## warte max 120ms, ansonsten kill
142
+ try do
143
+ rescue
144
+ reason -> { :error , reason }
145
+ end
146
+ fun . ( opts )
147
+
148
+ end
149
+
117
150
def connection ( pid ) do
118
151
:gen_statem . call ( pid , { :connection } )
119
152
end
@@ -136,43 +169,71 @@ defmodule Mongo.Session do
136
169
end
137
170
138
171
@ impl true
139
- def handle_event ( { :call , from } , { :start_transaction } , state , % Session { server_session: session } = data ) when state in [ :no_transaction , :transaction_aborted , :transaction_committed ] do
172
+ def handle_event ( { :call , from } ,
173
+ { :start_transaction } ,
174
+ transaction ,
175
+ % Session { server_session: session } = data ) when transaction in [ :no_transaction , :transaction_aborted , :transaction_committed ] do
140
176
{ :next_state , :starting_transaction , % Session { data | server_session: ServerSession . next_txn_num ( session ) } , { :reply , from , :ok } }
141
177
end
178
+
142
179
##
143
- # bind session: only if wire_version >= 6, MongoDB 3.6.x
180
+ # bind session: only if wire_version >= 6, MongoDB 3.6.x and no transaction is running: only lsid is added
144
181
#
145
- def handle_event ( { :call , from } , { :bind_session , cmd } , :no_transaction , % Session { conn: conn , wire_version: wire_version , server_session: % ServerSession { session_id: id } } ) when wire_version >= 6 do
182
+ def handle_event ( { :call , from } ,
183
+ { :bind_session , cmd } ,
184
+ transaction ,
185
+ % Session { conn: conn ,
186
+ wire_version: wire_version ,
187
+ server_session: % ServerSession { session_id: id } } ) when wire_version >= 6 and transaction in [ :no_transaction , :transaction_aborted , :transaction_committed ] do
146
188
{ :keep_state_and_data , { :reply , from , { :ok , conn , Keyword . merge ( cmd , lsid: % { id: id } ) } } }
147
189
end
148
- def handle_event ( { :call , from } , { :bind_session , cmd } , :starting_transaction ,
149
- % Session { conn: conn ,
150
- server_session: % ServerSession { session_id: id , txn_num: txn_num } ,
151
- wire_version: wire_version , opts: opts } = data ) when wire_version >= 6 do
190
+
191
+ def handle_event ( { :call , from } ,
192
+ { :bind_session , cmd } ,
193
+ :starting_transaction ,
194
+ % Session { conn: conn ,
195
+ server_session: % ServerSession { session_id: id , txn_num: txn_num } ,
196
+ wire_version: wire_version ,
197
+ opts: opts } = data ) when wire_version >= 6 do
152
198
result = Keyword . merge ( cmd ,
153
199
readConcern: Keyword . get ( opts , :read_concern ) ,
154
200
lsid: % { id: id } ,
155
201
txnNumber: % BSON.LongNumber { value: txn_num } ,
156
202
startTransaction: true ,
157
- autocommit: false ) |> filter_nils ( )
203
+ autocommit: false ) |> filter_nils ( ) |> Keyword . drop ( ~w( writeConcern) a )
204
+
158
205
{ :next_state , :transaction_in_progress , data , { :reply , from , { :ok , conn , result } } }
159
206
end
160
- def handle_event ( { :call , from } , { :bind_session , cmd } , :transaction_in_progress ,
161
- % Session { conn: conn , wire_version: wire_version ,
162
- server_session: % ServerSession { session_id: id , txn_num: txn_num } } ) when wire_version >= 6 do
207
+
208
+ def handle_event ( { :call , from } ,
209
+ { :bind_session , cmd } ,
210
+ :transaction_in_progress ,
211
+ % Session { conn: conn , wire_version: wire_version ,
212
+ server_session: % ServerSession { session_id: id , txn_num: txn_num } } ) when wire_version >= 6 do
163
213
result = Keyword . merge ( cmd ,
164
214
lsid: % { id: id } ,
165
215
txnNumber: % BSON.LongNumber { value: txn_num } ,
166
- autocommit: false )
216
+ autocommit: false ) |> Keyword . drop ( ~w ( writeConcern readConcern ) a )
167
217
{ :keep_state_and_data , { :reply , from , { :ok , conn , result } } }
168
218
end
169
- def handle_event ( { :call , from } , { :bind_session , cmd } , transaction , % Session { conn: conn } ) when transaction in [ :no_transaction , :starting_transaction , :transaction_in_progress ] do
219
+
220
+ # In case of wire_version < 6 we do nothing
221
+ def handle_event ( { :call , from } ,
222
+ { :bind_session , cmd } ,
223
+ _transaction ,
224
+ % Session { conn: conn } ) do
170
225
{ :keep_state_and_data , { :reply , from , { :ok , conn , cmd } } }
171
226
end
172
227
228
+ def handle_event ( { :call , from } , { :commit_transaction } , :starting_transaction , data ) do
229
+ { :next_state , :transaction_committed , data , { :reply , from , :ok } }
230
+ end
173
231
def handle_event ( { :call , from } , { :commit_transaction } , :transaction_in_progress , data ) do
174
232
{ :next_state , :transaction_committed , data , { :reply , from , run_commit_command ( data ) } }
175
233
end
234
+ def handle_event ( { :call , from } , { :abort_transaction } , :starting_transaction , data ) do
235
+ { :next_state , :transaction_aborted , data , { :reply , from , :ok } }
236
+ end
176
237
def handle_event ( { :call , from } , { :abort_transaction } , :transaction_in_progress , data ) do
177
238
{ :next_state , :transaction_aborted , data , { :reply , from , run_abort_command ( data ) } }
178
239
end
@@ -215,7 +276,7 @@ defmodule Mongo.Session do
215
276
lsid: % { id: id } ,
216
277
txnNumber: % BSON.LongNumber { value: txn_num } ,
217
278
autocommit: false ,
218
- writeConcern: Keyword . get ( opts , :write_concern ) ,
279
+ writeConcern: write_concern ( opts ) ,
219
280
maxTimeMS: Keyword . get ( opts , :max_commit_time_ms )
220
281
] |> filter_nils ( )
221
282
@@ -225,12 +286,13 @@ defmodule Mongo.Session do
225
286
defp run_abort_command ( % { conn: conn , server_session: % ServerSession { session_id: id , txn_num: txn_num } , opts: opts } ) do
226
287
227
288
Logger . debug ( "Running abort transaction" )
289
+
228
290
cmd = [
229
291
abortTransaction: 1 ,
230
292
lsid: % { id: id } ,
231
293
txnNumber: % BSON.LongNumber { value: txn_num } ,
232
294
autocommit: false ,
233
- writeConcern: Keyword . get ( opts , :write_concern ) ,
295
+ writeConcern: write_concern ( opts )
234
296
] |> filter_nils ( )
235
297
236
298
Mongo . exec_command ( conn , cmd , database: "admin" )
0 commit comments