我正在从c/cpp库运行一个UDP套接字,并从python传入一个回调。
回调运行良好,直到我尝试修改python应用程序的一个成员变量。当我尝试修改成员变量时,在任意时间之后,我会收到分段错误11。
我很好奇这是否意味着我需要通过在py_BEGIN_ALLOW_THREADS和py_END_ALLOW_THREADS:https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock中包装回调调用来处理GIL。
如果可能的话,我想避免包含,因为这是一个抽象的库,目的是与.net兼容
.cpp回调定义
#ifdef _WIN32
typedef void(__stdcall* UDPReceive)(const char* str);
#else
typedef void (*UDPReceive)(const char* str);
#endif.cpp线程启动
ReceiveThread = std::async(std::launch::async, &MLFUDP::ReceivePoller, this, callback);.h ReceiveCallback
UDPReceive ReceiveCallback = nullptr;.cpp接收触发python回调的线程
void UDP::ReceivePoller(UDPReceive callback)
{
ReceiveCallback = callback
ReceiverRunning = true;
UDPLock *receiveLock = new UDPLock();
#ifdef _WIN32
int socketLength = sizeof(ClientAddr);
int flags = 0;
#else
socklen_t socketLength = sizeof(ClientAddr);
int flags = MSG_WAITALL;
#endif
int result;
char buffer[MAXLINE];
while(ReceiverRunning)
{
try {
memset(buffer,'\0', MAXLINE);
result = recvfrom(RecvSocketDescriptor,
(char*)buffer,
MAXLINE,
flags,
(struct sockaddr*)&ClientAddr,
&socketLength);
#ifdef _WIN32
if (result == SOCKET_ERROR)
{
Log::LogErr("UDP Received error: " + std::to_string(WSAGetLastError()));
}
#else
if(result < 0)
{
Log::LogErr("UDD Received error: " + std::to_string(result));
}
#endif
buffer[result] = '\0';
#ifdef _WIN32
char* data = _strdup(buffer);
#else
char* data = strdup(buffer);
#endif
//handle overlfow
if(data == nullptr) {continue;}
receiveLock->Lock();
//Fire Callback
ReceiveCallback(data);
receiveLock->Unlock();
}
catch(...)
{
//okay, we want graceful exit when killing socket on close
}
}
}**.py库初始化**
def __init__(self, udp_recv_port, udp_send_port):
libname = ""
if platform == "win32":
print("On Windows")
libname = pathlib.Path(__file__).resolve().parent / "SDK_WIN.dll"
elif platform == "darwin":
print("on Mac")
libname = pathlib.Path(__file__).resolve().parent / "SDK.dylib"
print(libname)
elif platform == "linux":
print("on linux")
UDP_TYPE_BIND = 0
#Load dynamic library
self.sdk = CDLL(str(libname))
callback_type = CFUNCTYPE(None, c_char_p)
log_callback = callback_type(sdk_log_function)
self.sdk.InitLogging(2, log_callback)
recv_callback = callback_type(self.sdk_recv_callback)
self.sdk.InitUDP(udp_recv_port, udp_send_port, UDP_TYPE_BIND, recv_callback).py recv_callback definition如果我运行这个回调--一切正常,已经发送了几百万条消息。
@staticmethod
def sdk_recv_callback(message):
print(message.decode('utf-8'))
string_data = str(message.decode('utf-8'));
if len(string_data) < 1:
print("Returning")
return然而,如果我随后将此消息添加到线程安全FIFO queue.Queue()中,则在接收消息时在任意(短)时间内接收分段错误11。
@staticmethod
def sdk_recv_callback(message):
print(message.decode('utf-8'))
string_data = str(message.decode('utf-8'));
if len(string_data) < 1:
print("Returning")
return
message_queue.put(string_data).py poller函数吞食消息队列
def process_messages(self):
while self.is_running:
string_message = message_queue.get();
data = json.loads(string_message);
print(data)我大部分时间都在学习(在一个筒仓里),所以我认为我很有可能错过了一些基本的东西。我将非常感谢任何关于更好的方法或仅仅是另一组眼睛的建议。谢谢。
目前正在用m1芯片上的cmake在macos上进行编译。
发布于 2022-04-23 21:29:51
原来我不需要在c库中使用python.h来处理GIL。由于我使用的是ctype,所以每次调用回调(这里很详细)时,它都会通过旋转一个临时python线程来“神奇地”处理GIL。
这个seg错误是因为我从一个线程运行的process_message函数造成的。seg错误是因为我在类中初始化了ctype库而导致的。相反,我在main上插入SDK,并将一个引用传递给该类。
if __name__ == "__main__":
faulthandler.enable()
libname = ""
if platform == "win32":
print("On Windows")
libname = pathlib.Path(__file__).resolve().parent / "SDK_WIN.dll"
elif platform == "darwin":
print("on Mac")
libname = pathlib.Path(__file__).resolve().parent / "MLFSDK.dylib"
print(libname)
elif platform == "linux":
print("on linux")
sdk = CDLL(str(libname))
app = the_app(sdk,6666,7777)在这之后,所有的线程都在一起。
发布于 2022-04-19 09:10:58
嗯嗯。这个问题很复杂。我唯一能想到的就是UDP::ReceivePoller函数中的缓冲区溢出。您可以用char *声明char buffer[MAXLINE];。例如,假设MAXLINE = 1024。因此,buffer将是char *到内存库1024。很好。然后,您将memset buffer转到\0,长度为1024字节。很好。那你就做了
result = recvfrom(RecvSocketDescriptor,
(char*)buffer,
MAXLINE,
flags,
(struct sockaddr*)&ClientAddr,
&socketLength);理论上可以从套接字读取1024个最大字节。将1024返回到result和buffer中,设置为读取的1024字节。然后将缓冲区的buffer[result] = '\0';设置索引1024设置为null。但是,索引是从0开始的,而不是从1开始。因此,这将在1024保留为'\0‘之后设置一个字节。而且我想还不错(因为它只少了一个字节)。最终,buffer被放在了它不应该访问的内存中某个地方的旁边,并且它被塞住了。所以我的猜测是:
( a)将recvfrom(...)更新为仅准备好的MAXLINE - 1字节。这样,您只从套接字中准备了1023字节。在缓冲区中保留1024‘字节为null。
或
( b)将buffer更新为char buffer[MAXLINE + 1];以使1字节额外.(请记住将memset也更新为MAXLINE+1)
发布于 2022-04-23 17:49:52
部分问题可能是,由于您有一个seg错误,很难获得有关错误发生的位置的信息。
您可能希望在程序开始时使用import faulthandler并调用faulthander.enable() (参见https://docs.python.org/3/library/faulthandler.html#faulthandler.enable)。使用faulthandler可以提供一些关于seg故障的最小堆栈跟踪信息,并帮助您发现问题。
https://stackoverflow.com/questions/71807649
复制相似问题