Skip to content

Commit 1ed3f5b

Browse files
committed
csp event loop, async bridges
Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
1 parent ef9ff93 commit 1ed3f5b

35 files changed

Lines changed: 10384 additions & 113 deletions

conda/dev-environment-unix.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
- deprecated
1515
- docutils<0.22.1
1616
- exprtk
17+
- fastapi
1718
- flex
1819
- graphviz
1920
- gtest
@@ -57,6 +58,7 @@ dependencies:
5758
- twine
5859
- typing-extensions
5960
- unzip
61+
- uvicorn
6062
- wheel
6163
- zip
6264
- zlib

conda/dev-environment-win.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
- deprecated
1515
- docutils<0.22.1
1616
- exprtk
17+
- fastapi
1718
# - flex # not available on windows
1819
- graphviz
1920
- gtest
@@ -57,6 +58,7 @@ dependencies:
5758
- twine
5859
- typing-extensions
5960
# - unzip # not available on windows
61+
- uvicorn
6062
- wheel
6163
# - zip # not available on windows
6264
- zlib

cpp/csp/core/QueueWaiter.h

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
#ifndef _IN_CSP_CORE_QUEUEBLOCKINGWAIT_H
22
#define _IN_CSP_CORE_QUEUEBLOCKINGWAIT_H
33

4+
// Windows: winsock2.h must be included BEFORE windows.h to avoid redefinition errors
5+
#ifdef _WIN32
6+
#include <winsock2.h>
7+
#pragma comment(lib, "ws2_32.lib")
8+
#endif
9+
410
#include <mutex>
511
#include <condition_variable>
612
#include <csp/core/Time.h>
713
#include <csp/core/System.h>
814

15+
#ifdef __linux__
16+
#include <sys/eventfd.h>
17+
#include <unistd.h>
18+
#elif defined(__APPLE__)
19+
#include <unistd.h>
20+
#include <fcntl.h>
21+
#endif
22+
923
namespace csp
1024
{
1125

@@ -43,6 +57,197 @@ class QueueWaiter
4357
bool m_eventsPending;
4458
};
4559

60+
// FdWaiter provides file descriptor based signaling for integration with
61+
// external event loops like asyncio. The read fd can be registered with
62+
// select/poll/epoll and will become readable when notify() is called.
63+
class FdWaiter
64+
{
65+
public:
66+
FdWaiter()
67+
{
68+
#ifdef __linux__
69+
// Linux: use eventfd (single fd, most efficient)
70+
m_eventfd = eventfd( 0, EFD_NONBLOCK | EFD_CLOEXEC );
71+
m_readFd = m_eventfd;
72+
m_writeFd = m_eventfd;
73+
#elif defined(__APPLE__)
74+
// macOS: use pipe
75+
int fds[2];
76+
if( pipe( fds ) == 0 )
77+
{
78+
m_readFd = fds[0];
79+
m_writeFd = fds[1];
80+
// Set non-blocking
81+
fcntl( m_readFd, F_SETFL, O_NONBLOCK );
82+
fcntl( m_writeFd, F_SETFL, O_NONBLOCK );
83+
}
84+
else
85+
{
86+
m_readFd = -1;
87+
m_writeFd = -1;
88+
}
89+
#elif defined(_WIN32)
90+
// Windows: use socket pair (localhost loopback)
91+
m_readFd = INVALID_SOCKET;
92+
m_writeFd = INVALID_SOCKET;
93+
createSocketPair();
94+
#endif
95+
}
96+
97+
~FdWaiter()
98+
{
99+
#ifdef __linux__
100+
if( m_eventfd >= 0 )
101+
close( m_eventfd );
102+
#elif defined(__APPLE__)
103+
if( m_readFd >= 0 )
104+
close( m_readFd );
105+
if( m_writeFd >= 0 )
106+
close( m_writeFd );
107+
#elif defined(_WIN32)
108+
if( m_readFd != INVALID_SOCKET )
109+
closesocket( m_readFd );
110+
if( m_writeFd != INVALID_SOCKET )
111+
closesocket( m_writeFd );
112+
#endif
113+
}
114+
115+
// Get the file descriptor for select/poll registration
116+
// Returns -1 (or INVALID_SOCKET on Windows) if not available
117+
#ifdef _WIN32
118+
SOCKET readFd() const { return m_readFd; }
119+
#else
120+
int readFd() const { return m_readFd; }
121+
#endif
122+
123+
// Signal the fd (makes it readable)
124+
void notify()
125+
{
126+
std::lock_guard<std::mutex> guard( m_lock );
127+
if( m_notified )
128+
return; // Already notified, avoid filling buffer
129+
130+
m_notified = true;
131+
132+
#ifdef __linux__
133+
uint64_t val = 1;
134+
[[maybe_unused]] auto rv = write( m_eventfd, &val, sizeof( val ) );
135+
#elif defined(__APPLE__)
136+
char c = 1;
137+
[[maybe_unused]] auto rv = write( m_writeFd, &c, 1 );
138+
#elif defined(_WIN32)
139+
char c = 1;
140+
send( m_writeFd, &c, 1, 0 );
141+
#endif
142+
}
143+
144+
// Clear the notification (call after processing)
145+
void clear()
146+
{
147+
std::lock_guard<std::mutex> guard( m_lock );
148+
m_notified = false;
149+
150+
#ifdef __linux__
151+
uint64_t val;
152+
[[maybe_unused]] auto rv = read( m_eventfd, &val, sizeof( val ) );
153+
#elif defined(__APPLE__)
154+
char buf[64];
155+
while( read( m_readFd, buf, sizeof( buf ) ) > 0 ) {}
156+
#elif defined(_WIN32)
157+
char buf[64];
158+
while( recv( m_readFd, buf, sizeof( buf ), 0 ) > 0 ) {}
159+
#endif
160+
}
161+
162+
bool isValid() const
163+
{
164+
#ifdef _WIN32
165+
return m_readFd != INVALID_SOCKET;
166+
#else
167+
return m_readFd >= 0;
168+
#endif
169+
}
170+
171+
private:
172+
#ifdef _WIN32
173+
void createSocketPair()
174+
{
175+
// Create a listening socket on localhost
176+
SOCKET listener = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
177+
if( listener == INVALID_SOCKET )
178+
return;
179+
180+
struct sockaddr_in addr;
181+
memset( &addr, 0, sizeof( addr ) );
182+
addr.sin_family = AF_INET;
183+
addr.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
184+
addr.sin_port = 0; // Let OS pick a port
185+
186+
if( bind( listener, (struct sockaddr*)&addr, sizeof( addr ) ) == SOCKET_ERROR )
187+
{
188+
closesocket( listener );
189+
return;
190+
}
191+
192+
int addrlen = sizeof( addr );
193+
if( getsockname( listener, (struct sockaddr*)&addr, &addrlen ) == SOCKET_ERROR )
194+
{
195+
closesocket( listener );
196+
return;
197+
}
198+
199+
if( listen( listener, 1 ) == SOCKET_ERROR )
200+
{
201+
closesocket( listener );
202+
return;
203+
}
204+
205+
// Create client socket and connect
206+
m_writeFd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
207+
if( m_writeFd == INVALID_SOCKET )
208+
{
209+
closesocket( listener );
210+
return;
211+
}
212+
213+
if( connect( m_writeFd, (struct sockaddr*)&addr, sizeof( addr ) ) == SOCKET_ERROR )
214+
{
215+
closesocket( m_writeFd );
216+
closesocket( listener );
217+
m_writeFd = INVALID_SOCKET;
218+
return;
219+
}
220+
221+
// Accept the connection
222+
m_readFd = accept( listener, NULL, NULL );
223+
closesocket( listener ); // Done with listener
224+
225+
if( m_readFd == INVALID_SOCKET )
226+
{
227+
closesocket( m_writeFd );
228+
m_writeFd = INVALID_SOCKET;
229+
return;
230+
}
231+
232+
// Set non-blocking
233+
u_long mode = 1;
234+
ioctlsocket( m_readFd, FIONBIO, &mode );
235+
ioctlsocket( m_writeFd, FIONBIO, &mode );
236+
}
237+
238+
SOCKET m_readFd;
239+
SOCKET m_writeFd;
240+
#else
241+
int m_readFd;
242+
int m_writeFd;
243+
#ifdef __linux__
244+
int m_eventfd;
245+
#endif
246+
#endif
247+
std::mutex m_lock;
248+
bool m_notified = false;
249+
};
250+
46251
}
47252

48253
#endif

0 commit comments

Comments
 (0)