13
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
# See the License for the specific language governing permissions and
15
15
# limitations under the License.
16
+ import inspect
16
17
import logging
17
18
import time
19
+ import types
18
20
from collections import defaultdict
19
21
from sys import intern
20
22
from time import monotonic as monotonic_time
@@ -526,6 +528,12 @@ def new_transaction(
526
528
the function will correctly handle being aborted and retried half way
527
529
through its execution.
528
530
531
+ Similarly, the arguments to `func` (`args`, `kwargs`) should not be generators,
532
+ since they could be evaluated multiple times (which would produce an empty
533
+ result on the second or subsequent evaluation). Likewise, the closure of `func`
534
+ must not reference any generators. This method attempts to detect such usage
535
+ and will log an error.
536
+
529
537
Args:
530
538
conn
531
539
desc
@@ -536,6 +544,39 @@ def new_transaction(
536
544
**kwargs
537
545
"""
538
546
547
+ # Robustness check: ensure that none of the arguments are generators, since that
548
+ # will fail if we have to repeat the transaction.
549
+ # For now, we just log an error, and hope that it works on the first attempt.
550
+ # TODO: raise an exception.
551
+ for i , arg in enumerate (args ):
552
+ if inspect .isgenerator (arg ):
553
+ logger .error (
554
+ "Programming error: generator passed to new_transaction as "
555
+ "argument %i to function %s" ,
556
+ i ,
557
+ func ,
558
+ )
559
+ for name , val in kwargs .items ():
560
+ if inspect .isgenerator (val ):
561
+ logger .error (
562
+ "Programming error: generator passed to new_transaction as "
563
+ "argument %s to function %s" ,
564
+ name ,
565
+ func ,
566
+ )
567
+ # also check variables referenced in func's closure
568
+ if inspect .isfunction (func ):
569
+ f = cast (types .FunctionType , func )
570
+ if f .__closure__ :
571
+ for i , cell in enumerate (f .__closure__ ):
572
+ if inspect .isgenerator (cell .cell_contents ):
573
+ logger .error (
574
+ "Programming error: function %s references generator %s "
575
+ "via its closure" ,
576
+ f ,
577
+ f .__code__ .co_freevars [i ],
578
+ )
579
+
539
580
start = monotonic_time ()
540
581
txn_id = self ._TXN_ID
541
582
@@ -1226,9 +1267,9 @@ async def simple_upsert_many(
1226
1267
self ,
1227
1268
table : str ,
1228
1269
key_names : Collection [str ],
1229
- key_values : Collection [Iterable [Any ]],
1270
+ key_values : Collection [Collection [Any ]],
1230
1271
value_names : Collection [str ],
1231
- value_values : Iterable [ Iterable [Any ]],
1272
+ value_values : Collection [ Collection [Any ]],
1232
1273
desc : str ,
1233
1274
) -> None :
1234
1275
"""
@@ -1920,7 +1961,7 @@ async def simple_delete_many(
1920
1961
self ,
1921
1962
table : str ,
1922
1963
column : str ,
1923
- iterable : Iterable [Any ],
1964
+ iterable : Collection [Any ],
1924
1965
keyvalues : Dict [str , Any ],
1925
1966
desc : str ,
1926
1967
) -> int :
@@ -1931,7 +1972,8 @@ async def simple_delete_many(
1931
1972
Args:
1932
1973
table: string giving the table name
1933
1974
column: column name to test for inclusion against `iterable`
1934
- iterable: list
1975
+ iterable: list of values to match against `column`. NB cannot be a generator
1976
+ as it may be evaluated multiple times.
1935
1977
keyvalues: dict of column names and values to select the rows with
1936
1978
desc: description of the transaction, for logging and metrics
1937
1979
0 commit comments