Repository navigation
Expand file tree
/
Copy pathmodRestPump.bas
More file actions
150 lines (129 loc) · 4.99 KB
/
Copy pathmodRestPump.bas
File metadata and controls
150 lines (129 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
Attribute VB_Name = "modRestPump"
Option Explicit
' modRestPump - module-level anchor for in-flight async calls plus
' the Win32 SetTimer polling pump that drives their completion.
'
' Internal-ish module: users normally go through modRest, but this
' module's Cancel functions are also exposed as modRest.Cancel / .CancelAll
' for convenience.
'
' Two responsibilities, kept together because each exists for the other:
'
' 1. Registry: Register / RemovePending / IsPending / Cancel /
' CancelAll / PendingCount. Keeps a Dictionary keyed by Long
' request-id; without this anchor the caller-less clsAsyncCall
' would be released the moment SendAsync returns.
'
' 2. Polling pump: EnsurePump / TimerProc. Starts a Win32 WM_TIMER
' whose callback iterates the registry on each tick and asks each
' pending call to poll its own readyState. The pump self-stops
' when the registry empties.
'
' Why polling instead of MSXML's onreadystatechange event sink: four
' COM-sink approaches were tried and all failed on this Access/MSXML
' build (see async_architecture.md). Polling sidesteps the whole class
' of issues at the cost of ~25ms completion latency.
' --- Win32 declares (MUST come before any Sub/Function in a VBA module) ---
Public Declare PtrSafe Function SetTimer Lib "user32" ( _
ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As LongPtr) As LongPtr
Public Declare PtrSafe Function KillTimer Lib "user32" ( _
ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr) As Long
' 25ms gives ~40Hz polling. Far below WM_TIMER coalescing thresholds,
' well below human-perceptible latency, and only one COM readyState
' read per tick per pending call -- effectively free at typical
' usage (1-5 concurrent requests).
Private Const POLL_INTERVAL_MS As Long = 25
Private g_pending As Object ' Scripting.Dictionary, key = Long requestId
Private g_nextId As Long
Private g_timerId As LongPtr
'==========================================================================
' Registry
'==========================================================================
Public Function Register(ByVal asyncCall As Object) As Long
EnsureDict
g_nextId = g_nextId + 1
g_pending.Add g_nextId, asyncCall
Register = g_nextId
End Function
Public Sub RemovePending(ByVal id As Long)
If g_pending Is Nothing Then Exit Sub
If g_pending.Exists(id) Then g_pending.Remove id
End Sub
Public Function PendingCount() As Long
If g_pending Is Nothing Then Exit Function
PendingCount = g_pending.Count
End Function
Public Function IsPending(ByVal id As Long) As Boolean
If g_pending Is Nothing Then Exit Function
IsPending = g_pending.Exists(id)
End Function
Public Sub Cancel(ByVal id As Long)
If g_pending Is Nothing Then Exit Sub
If Not g_pending.Exists(id) Then Exit Sub
Dim asyncCall As Object
Set asyncCall = g_pending(id)
On Error Resume Next
asyncCall.Abort ' clsAsyncCall.Abort
On Error GoTo 0
g_pending.Remove id
End Sub
Public Sub CancelAll()
If g_pending Is Nothing Then Exit Sub
Dim ids As Variant
ids = g_pending.Keys
Dim i As Long
For i = LBound(ids) To UBound(ids)
Cancel CLng(ids(i))
Next i
End Sub
Private Sub EnsureDict()
If g_pending Is Nothing Then Set g_pending = CreateObject("Scripting.Dictionary")
End Sub
'==========================================================================
' Polling pump
'==========================================================================
' Called by clsAsyncCall.Run right after m_http.send. Idempotent;
' starts the WM_TIMER if it isn't already running. The timer stops
' itself once g_pending is empty.
Public Sub EnsurePump()
If g_timerId <> 0 Then Exit Sub
g_timerId = SetTimer(0&, 0&, POLL_INTERVAL_MS, AddressOf TimerProc)
End Sub
Private Sub StopPump()
If g_timerId = 0 Then Exit Sub
KillTimer 0&, g_timerId
g_timerId = 0
End Sub
' WM_TIMER callback. MUST be Public so AddressOf can resolve it. MUST NOT
' let a VBA error escape - an unhandled error inside a Win32 callback
' takes Access down with it.
Public Sub TimerProc(ByVal hWnd As LongPtr, _
ByVal uMsg As Long, _
ByVal idEvent As LongPtr, _
ByVal dwTime As Long)
On Error Resume Next
If g_pending Is Nothing Or PendingCount() = 0 Then
StopPump
Exit Sub
End If
' Snapshot keys before iterating: PollAndMaybeDispatch removes
' completed entries from g_pending and would otherwise invalidate
' the live key sequence.
Dim ids As Variant
ids = g_pending.Keys
Dim i As Long
For i = LBound(ids) To UBound(ids)
Dim id As Long
id = CLng(ids(i))
If g_pending.Exists(id) Then
Dim asyncCall As Object
Set asyncCall = g_pending(id)
If Not asyncCall Is Nothing Then asyncCall.PollAndMaybeDispatch
End If
Next i
If PendingCount() = 0 Then StopPump
End Sub