]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2017 SUSE LLC | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
3efd9988 | 15 | #pragma once |
31f18b77 | 16 | |
b3b6e05e TL |
17 | #include <cassert> |
18 | #include <functional> | |
19 | ||
3efd9988 FG |
20 | struct _ts; |
21 | typedef struct _ts PyThreadState; | |
31f18b77 | 22 | |
3efd9988 | 23 | #include <pthread.h> |
31f18b77 | 24 | |
3efd9988 FG |
25 | |
26 | /** | |
27 | * Wrap PyThreadState to carry a record of which POSIX thread | |
28 | * the thread state relates to. This allows the Gil class to | |
29 | * validate that we're being used from the right thread. | |
30 | */ | |
31 | class SafeThreadState | |
32 | { | |
33 | public: | |
11fdf7f2 | 34 | explicit SafeThreadState(PyThreadState *ts_); |
3efd9988 FG |
35 | |
36 | SafeThreadState() | |
37 | : ts(nullptr), thread(0) | |
38 | { | |
39 | } | |
40 | ||
41 | PyThreadState *ts; | |
42 | pthread_t thread; | |
43 | ||
44 | void set(PyThreadState *ts_) | |
45 | { | |
46 | ts = ts_; | |
47 | thread = pthread_self(); | |
48 | } | |
49 | }; | |
31f18b77 FG |
50 | |
51 | // | |
52 | // Use one of these in any scope in which you need to hold Python's | |
53 | // Global Interpreter Lock. | |
54 | // | |
55 | // Do *not* nest these, as a second GIL acquire will deadlock (see | |
56 | // https://docs.python.org/2/c-api/init.html#c.PyEval_RestoreThread) | |
57 | // | |
58 | // If in doubt, explicitly put a scope around the block of code you | |
59 | // know you need the GIL in. | |
60 | // | |
3efd9988 | 61 | // See the comment in Gil::Gil for when to set new_thread == true |
31f18b77 FG |
62 | // |
63 | class Gil { | |
64 | public: | |
65 | Gil(const Gil&) = delete; | |
66 | Gil& operator=(const Gil&) = delete; | |
67 | ||
3efd9988 FG |
68 | Gil(SafeThreadState &ts, bool new_thread = false); |
69 | ~Gil(); | |
31f18b77 FG |
70 | |
71 | private: | |
3efd9988 | 72 | SafeThreadState &pThreadState; |
31f18b77 FG |
73 | PyThreadState *pNewThreadState = nullptr; |
74 | }; | |
75 | ||
b3b6e05e TL |
76 | // because the Python runtime could relinquish the GIL when performing GC |
77 | // and re-acquire it afterwards, we should enforce following locking policy: | |
78 | // 1. do not acquire locks when holding the GIL, use a without_gil or | |
79 | // without_gil_t to guard the code which acquires non-gil locks. | |
80 | // 2. always hold a GIL when calling python functions, for example, when | |
81 | // constructing a PyFormatter instance. | |
82 | // | |
83 | // a wrapper that provides a convenient RAII-style mechinary for acquiring | |
84 | // and releasing GIL, like the macros of Py_BEGIN_ALLOW_THREADS and | |
85 | // Py_END_ALLOW_THREADS. | |
86 | struct without_gil_t { | |
87 | without_gil_t(); | |
88 | ~without_gil_t(); | |
b3b6e05e TL |
89 | void release_gil(); |
90 | void acquire_gil(); | |
20effc67 | 91 | private: |
b3b6e05e TL |
92 | PyThreadState *save = nullptr; |
93 | friend struct with_gil_t; | |
94 | }; | |
95 | ||
96 | struct with_gil_t { | |
97 | with_gil_t(without_gil_t& allow_threads); | |
98 | ~with_gil_t(); | |
99 | private: | |
100 | without_gil_t& allow_threads; | |
101 | }; | |
102 | ||
103 | // invoke func with GIL acquired | |
104 | template<typename Func> | |
105 | auto with_gil(without_gil_t& no_gil, Func&& func) { | |
106 | with_gil_t gil{no_gil}; | |
107 | return std::invoke(std::forward<Func>(func)); | |
108 | } | |
109 | ||
110 | template<typename Func> | |
111 | auto without_gil(Func&& func) { | |
112 | without_gil_t no_gil; | |
113 | return std::invoke(std::forward<Func>(func)); | |
114 | } |