Skip to content

Commit d07d3ac

Browse files
Add process forking warning to Python Core SDK documentation
- Add comprehensive warning about not forking processes after Statsig initialization - Include technical explanation of threading/async runtime issues - Provide correct examples for uWSGI and Gunicorn deployment scenarios - Show incorrect initialization pattern to avoid - Offer lazy initialization as alternative solution - Addresses potential deadlock scenarios in production WSGI deployments Co-Authored-By: xin@statsig.com <xin@statsig.com>
1 parent 8fad7bb commit d07d3ac

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

docs/server-core/python/_faqs.mdx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
## Process Forking and WSGI Servers
2+
3+
### ⚠️ Warning: Do not fork processes after Statsig initialization
4+
5+
**Important:** Never fork processes after calling `statsig.initialize()`. Doing so will put Statsig in a weird state and may cause deadlocks.
6+
7+
The Python Core SDK uses internal threading and async runtime components that do not work correctly when copied across process boundaries. When a process forks after initialization, these components can become corrupted, leading to:
8+
9+
- Deadlocks in event logging
10+
- Hanging initialization calls
11+
- Unpredictable SDK behavior
12+
- Silent failures in feature evaluation
13+
14+
### Correct initialization for WSGI servers
15+
16+
For production deployments using WSGI servers like uWSGI or Gunicorn, ensure Statsig is initialized **after** the worker processes are forked, not in the main process.
17+
18+
#### ✅ Correct: uWSGI example
19+
20+
```python
21+
# app.py
22+
from statsig_python_core import Statsig, StatsigOptions
23+
from flask import Flask
24+
25+
app = Flask(__name__)
26+
statsig = None
27+
28+
def init_statsig():
29+
global statsig
30+
if statsig is None:
31+
options = StatsigOptions()
32+
options.environment = "production"
33+
statsig = Statsig("your-server-secret-key", options)
34+
statsig.initialize().wait()
35+
36+
# Initialize in each worker process
37+
@app.before_first_request
38+
def before_first_request():
39+
init_statsig()
40+
41+
@app.route('/')
42+
def index():
43+
# Use statsig here
44+
return "Hello World"
45+
```
46+
47+
```ini
48+
# uwsgi.ini
49+
[uwsgi]
50+
module = app:app
51+
master = true
52+
processes = 4
53+
# Statsig will be initialized in each worker process
54+
```
55+
56+
#### ✅ Correct: Gunicorn example
57+
58+
```python
59+
# gunicorn_config.py
60+
def post_fork(server, worker):
61+
# Initialize Statsig after worker process is forked
62+
from app import init_statsig
63+
init_statsig()
64+
65+
# app.py - same as uWSGI example above
66+
```
67+
68+
```bash
69+
# Start Gunicorn with post-fork hook
70+
gunicorn --config gunicorn_config.py app:app
71+
```
72+
73+
#### ❌ Incorrect: Initializing before fork
74+
75+
```python
76+
# DON'T DO THIS - initializing in main process before fork
77+
from statsig_python_core import Statsig, StatsigOptions
78+
79+
# This runs in the main process
80+
options = StatsigOptions()
81+
options.environment = "production"
82+
statsig = Statsig("your-server-secret-key", options)
83+
statsig.initialize().wait() # ❌ This will cause issues when workers fork
84+
85+
app = Flask(__name__)
86+
# Workers inherit the initialized statsig instance - this causes problems!
87+
```
88+
89+
### Alternative: Use lazy initialization
90+
91+
If you cannot control the forking behavior, use lazy initialization to ensure Statsig is only initialized when first needed in each process:
92+
93+
```python
94+
import threading
95+
from statsig_python_core import Statsig, StatsigOptions
96+
97+
_statsig = None
98+
_statsig_lock = threading.Lock()
99+
100+
def get_statsig():
101+
global _statsig
102+
if _statsig is None:
103+
with _statsig_lock:
104+
if _statsig is None: # Double-check locking
105+
options = StatsigOptions()
106+
options.environment = "production"
107+
_statsig = Statsig("your-server-secret-key", options)
108+
_statsig.initialize().wait()
109+
return _statsig
110+
111+
# Use get_statsig() instead of direct statsig access
112+
@app.route('/')
113+
def index():
114+
statsig = get_statsig()
115+
# Use statsig here
116+
return "Hello World"
117+
```

0 commit comments

Comments
 (0)