mbuzz 0.8.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHECK_BUG.md DELETED
@@ -1,168 +0,0 @@
1
- # Thread-Safety Bug Fix Verification
2
-
3
- ## Bug Summary
4
-
5
- **Issue**: Middleware used instance variables (`@session_id`, `@visitor_id`, `@request`) shared across concurrent requests in multi-threaded servers (Puma). This caused race conditions where session/visitor IDs leaked between requests.
6
-
7
- **Impact**: Pet Resorts Australia had **178,428 sessions** for only **172,000 visitors**. Some visitors had 1,500+ sessions because cookies were set with wrong session_ids under concurrent load.
8
-
9
- **Root Cause**: Rack middleware is instantiated once and shared across all requests. Instance variables are not thread-safe.
10
-
11
- ## Fix Details
12
-
13
- | Field | Value |
14
- |-------|-------|
15
- | **Fixed in version** | 0.6.3 |
16
- | **Commit** | `bdf4c64` |
17
- | **Fix deployed** | 2025-12-22 ~21:30 UTC (2025-12-23 ~08:30 AEDT) |
18
- | **Gem published** | 2025-12-23 |
19
-
20
- ## Verification Checklist
21
-
22
- ### After 24-48 hours (by 2025-12-25):
23
-
24
- Run these queries in production Rails console:
25
-
26
- ```ruby
27
- # 1. Check session creation rate AFTER fix
28
- # Should see dramatically fewer sessions per hour
29
- cutoff = Time.parse("2025-12-23 08:30:00 UTC") # Adjust to actual deploy time
30
-
31
- puts "Sessions BEFORE fix (last 24h before deploy):"
32
- before_sessions = Session.where(created_at: (cutoff - 24.hours)..cutoff).count
33
- puts " Count: #{before_sessions}"
34
-
35
- puts "\nSessions AFTER fix (24h after deploy):"
36
- after_sessions = Session.where(created_at: cutoff..(cutoff + 24.hours)).count
37
- puts " Count: #{after_sessions}"
38
-
39
- puts "\nReduction: #{((before_sessions - after_sessions).to_f / before_sessions * 100).round(1)}%"
40
- ```
41
-
42
- ```ruby
43
- # 2. Check sessions per visitor ratio
44
- # Should be close to 1.0-1.5 for new visitors (was 1.05 overall but outliers had 1500+)
45
- cutoff = Time.parse("2025-12-23 08:30:00 UTC")
46
-
47
- new_visitors = Visitor.where("created_at > ?", cutoff)
48
- new_visitor_ids = new_visitors.pluck(:id)
49
-
50
- sessions_for_new = Session.where(visitor_id: new_visitor_ids).count
51
- puts "New visitors since fix: #{new_visitors.count}"
52
- puts "Sessions for new visitors: #{sessions_for_new}"
53
- puts "Ratio: #{(sessions_for_new.to_f / new_visitors.count).round(2)}"
54
- ```
55
-
56
- ```ruby
57
- # 3. Check for any new outliers (visitors with 10+ sessions in 24h)
58
- cutoff = Time.parse("2025-12-23 08:30:00 UTC")
59
-
60
- outliers = Session.where("created_at > ?", cutoff)
61
- .group(:visitor_id)
62
- .having("count(*) > 10")
63
- .count
64
-
65
- puts "Visitors with 10+ sessions since fix: #{outliers.count}"
66
- outliers.sort_by { |_, v| -v }.first(5).each do |vid, count|
67
- puts " Visitor #{vid}: #{count} sessions"
68
- end
69
- ```
70
-
71
- ### Expected Results After Fix:
72
-
73
- - [ ] Session creation rate drops by 90%+
74
- - [ ] Sessions per new visitor ratio < 2.0
75
- - [ ] No new outliers with 100+ sessions
76
- - [ ] Cookie session_id matches env session_id (verified by tests)
77
-
78
- ---
79
-
80
- ## Other SDKs to Review
81
-
82
- **CRITICAL**: Check all other SDKs for the same thread-safety bug!
83
-
84
- ### SDK Review Checklist:
85
-
86
- | SDK | Location | Status | Reviewed By | Date |
87
- |-----|----------|--------|-------------|------|
88
- | mbuzz-ruby | `/Users/vlad/code/m/mbuzz-ruby` | FIXED | Claude | 2025-12-22 |
89
- | mbuzz-python | `/Users/vlad/code/m/mbuzz-python` | SAFE | Claude | 2025-12-22 |
90
- | mbuzz-php | `/Users/vlad/code/m/mbuzz-php` | SAFE | Claude | 2025-12-22 |
91
- | mbuzz-node | `/Users/vlad/code/m/mbuzz-node` | SAFE | Claude | 2025-12-22 |
92
-
93
- ### Review Results:
94
-
95
- **mbuzz-python**: SAFE
96
- - Uses `contextvars.ContextVar` for thread-safe context storage
97
- - Uses Flask's `g` object for request-scoped storage
98
- - Local variables used throughout middleware
99
- - Async session creation captures values in local variables before spawning thread
100
-
101
- **mbuzz-php**: SAFE
102
- - PHP is single-process per request by default
103
- - No shared state between requests
104
- - Each request gets fresh instance of everything
105
-
106
- **mbuzz-node**: SAFE
107
- - Uses `AsyncLocalStorage` from `node:async_hooks` for async request isolation
108
- - Express middleware uses local variables (`visitor`, `session`, `secure`)
109
- - Attaches data to request-scoped `req.mbuzz` object
110
- - `createSessionAsync` captures values as function parameters before `setImmediate`
111
- - Node.js is single-threaded, so race conditions are inherently less likely
112
-
113
- ### What to Look For:
114
-
115
- 1. **Middleware/Handler using instance variables or class variables for request-specific data**
116
- - BAD: `self.session_id = ...` or `@session_id = ...`
117
- - GOOD: Local variables passed through function calls
118
-
119
- 2. **Mutable shared state**
120
- - BAD: Global or class-level dicts/hashes storing request data
121
- - GOOD: Request-scoped context objects or local variables
122
-
123
- 3. **Thread-local storage without proper cleanup**
124
- - Check that thread-local data is cleared after each request
125
-
126
- ### Python-specific concerns:
127
- - Check for module-level variables
128
- - Check Flask/Django middleware for shared state
129
- - WSGI apps can have similar issues with global state
130
-
131
- ### PHP-specific concerns:
132
- - PHP is typically single-threaded per request, so likely SAFE
133
- - But check for any persistent worker modes (Swoole, RoadRunner, FrankenPHP)
134
-
135
- ### Node.js-specific concerns:
136
- - Node is single-threaded, so likely SAFE
137
- - But check for any shared state in closures or module scope
138
-
139
- ---
140
-
141
- ## Data Cleanup (Optional)
142
-
143
- After verifying the fix works, consider cleaning up the bad data:
144
-
145
- ```ruby
146
- # Find sessions with no events (likely created by the bug)
147
- # BE CAREFUL - only run after thorough analysis
148
-
149
- # Count empty sessions by account
150
- Account.find_each do |account|
151
- session_ids_with_events = account.events.distinct.pluck(:session_id)
152
- empty_sessions = account.sessions.where.not(session_id: session_ids_with_events).count
153
- total_sessions = account.sessions.count
154
-
155
- next if empty_sessions == 0
156
-
157
- puts "#{account.name}: #{empty_sessions}/#{total_sessions} empty sessions (#{(empty_sessions.to_f/total_sessions*100).round(1)}%)"
158
- end
159
- ```
160
-
161
- ---
162
-
163
- ## Notes
164
-
165
- - Bug was discovered via dashboard metrics investigation (avg visits showing 28.5 with 0.6 avg days)
166
- - Traced to Pet Resorts Australia account (PetPro360)
167
- - Logs showed session creation every few seconds with different session_ids
168
- - Test added: `test_race_condition_with_slow_app` - 49/50 failures before fix, 0 after
data/lib/.DS_Store DELETED
Binary file