Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

update

  • Loading branch information...
commit 7d54397938213f50bff8b3db0285c257b22a799e 1 parent 1e2d2b3
yayanyang authored
View
8 io/abi.cpp
@@ -1,12 +1,10 @@
#include <lemon/io/abi.h>
#ifdef LEMON_IO_IOCP
#include <lemon/io/io_service_iocp.hpp>
-#else
-#include <lemon/io/io_service_reactor.hpp>
+#elif defined(LEMON_IO_EPOLL)
+#include <lemon/io/io_service_epoll.hpp>
#endif //
-#ifdef LEMON_NULL
-
using namespace lemon;
using namespace lemon::io;
@@ -480,5 +478,3 @@ LEMON_IO_API
*errorCode = e;
}
}
-
-#endif
View
3  io/abi.h
@@ -18,9 +18,10 @@
#define LEMON_IO_CURRENT 0x03
-
#define LEMON_IO_REACTOR_SESSIONS 1024
+#define LEMON_IO_EPOLL_MAX_EVENTS 10
+
//////////////////////////////////////////////////////////////////////////
typedef struct LemonIoWriter{
View
2  io/configure.h.in
@@ -1,5 +1,7 @@
#cmakedefine LEMON_IO_IOCP
#cmakedefine LEMON_IO_EPOLL
#cmakedefine LEMON_IO_KQUEUE
+#cmakedefine LEMON_HAS_FCNTL_H
+#cmakedefine LEMON_HAS_EVENTFD_H
View
123 io/io_service_epoll.cpp
@@ -0,0 +1,123 @@
+#include <errno.h>
+#include <lemonxx/function/bind.hpp>
+#include <lemon/io/io_service_epoll.hpp>
+
+#ifdef LEMON_IO_EPOLL
+
+#include <unistd.h>
+#include <sys/epoll.h>
+#ifdef LEMON_HAS_EVENTFD_H
+#include <sys/eventfd.h>
+#else
+# error "not support platform"
+#endif
+
+namespace lemon{namespace io{namespace core{
+
+ io_service::io_service()
+ :_exit(false),_eventfd(-1),_epollfd(-1)
+ {
+ try
+ {
+ _eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+
+ if(-1 == _eventfd)
+ {
+ error_info errorCode;
+
+ LEMON_POSIX_ERROR(errorCode,errno);
+
+ errorCode.check_throw();
+ }
+
+ _epollfd = epoll_create(1);
+
+ if(-1 == _epollfd)
+ {
+ error_info errorCode;
+
+ LEMON_POSIX_ERROR(errorCode,errno);
+
+ errorCode.check_throw();
+ }
+
+ epoll_data data;
+
+ data.ptr = NULL;
+
+ epoll_event epevent = {EPOLLIN,data};
+
+ if(-1 == epoll_ctl(_epollfd, EPOLL_CTL_ADD, _eventfd, &epevent))
+ {
+ error_info errorCode;
+
+ LEMON_POSIX_ERROR(errorCode,errno);
+
+ errorCode.check_throw();
+ }
+
+ _ioworker.start(lemon::bind(&io_service::epoll_loop,this));
+ }
+ catch(...)
+ {
+ if(_epollfd != -1) close(_epollfd);
+
+ if(_eventfd != -1) close(_eventfd);
+
+ throw;
+ }
+
+
+ }
+
+ io_service::~io_service()
+ {
+ eventfd_write(_eventfd, 1);
+
+ _ioworker.join();
+
+ if(_epollfd != -1) close(_epollfd);
+
+ if(_eventfd != -1) close(_eventfd);
+ }
+
+ void io_service::epoll_loop()
+ {
+ epoll_event events[LEMON_IO_EPOLL_MAX_EVENTS];
+
+ for(;;)
+ {
+ int result = 0;
+
+ result = epoll_wait(_epollfd, events, LEMON_IO_EPOLL_MAX_EVENTS, -1);
+
+ if(-1 == result)
+ {
+ if(EINTR != errno)
+ {
+ error_info errorCode;
+
+ LEMON_POSIX_ERROR(errorCode,errno);
+
+ errorCode.check_throw();
+ }
+ else
+ {
+ perror(strerror(errno));
+
+ return;
+ }
+ }
+
+ for(int i = 0; i < result; ++ i)
+ {
+ if(events[i].data.ptr == NULL) return;
+
+ post_one((io_data *)events[i].data.ptr);
+ }
+ }
+ }
+
+}}}
+
+#endif //LEMON_IO_EPOLL
View
61 io/io_service_epoll.hpp
@@ -9,5 +9,66 @@
#ifndef LEMON_IO_SERVICE_EPOLL_HPP
#define LEMON_IO_SERVICE_EPOLL_HPP
+#include <lemon/io/socket_reactor.hpp>
+
+#ifdef LEMON_IO_EPOLL
+
+#include <lemon/io/io_service_reactor.hpp>
+
+namespace lemon{namespace io{namespace core{
+
+ class io_service : public basic_io_service<io_service,socket>
+ {
+ public:
+
+ io_service();
+
+ ~io_service();
+
+ void reset(){ _reactor.reset(); }
+
+ void attach() { _reactor.attach(); }
+
+ void detach() { _reactor.detach(); }
+
+ void post_one(LemonIOCallback callback,void * userdata,LemonErrorInfo *errorCode)
+ {
+ _reactor.post_one(callback, userdata, 0, 0, errorCode);
+ }
+
+ void post_one(io_data * iodata,LemonErrorInfo *errorCode)
+ {
+ _reactor.post_one(iodata,errorCode);
+ }
+
+ void post_one(io_data * iodata)
+ {
+ error_info errorCode;
+
+ _reactor.post_one(iodata,errorCode);
+
+ errorCode.check_throw();
+ }
+
+ private:
+
+ void epoll_loop();
+
+ private:
+
+ bool _exit;
+
+ io_service_reactor _reactor;
+
+ thread_t _ioworker;
+
+ int _eventfd;
+
+ int _epollfd;
+ };
+
+}}}
+
+#endif //
#endif //LEMON_IO_SERVICE_EPOLL_HPP
View
14 io/io_service_iocp.cpp
@@ -9,9 +9,11 @@ namespace lemon{namespace io{namespace core{
{
if(NULL == _completionPort)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_WIN32_ERROR(errorCode,GetLastError());
+
+ errorCode.check_throw();
}
}
@@ -40,9 +42,11 @@ namespace lemon{namespace io{namespace core{
{
if(NULL == iodata)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_WIN32_ERROR(errorCode,GetLastError());
+
+ errorCode.check_throw();
}
LEMON_WIN32_ERROR(errorCode,GetLastError());
@@ -128,9 +132,11 @@ namespace lemon{namespace io{namespace core{
{
if(NULL == CreateIoCompletionPort(handle,_completionPort,completekey,0))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_WIN32_ERROR(errorCode,GetLastError());
+
+ errorCode.check_throw();
}
}
@@ -179,7 +185,7 @@ namespace lemon{namespace io{namespace core{
void accept_io_data::callback(void *userData,size_t numberOfBytesTransferred,const LemonErrorInfo * ec)
{
- scope_error_info errorCode(ec);
+ error_info errorCode(ec);
accept_io_data * self = (accept_io_data*)userData;
View
73 io/io_service_reactor.cpp
@@ -4,10 +4,10 @@
namespace lemon{namespace io{namespace core{
- io_data::io_data(size_t type):_type(type) {}
+ io_data::io_data(size_t type, int fd):_type(type),_fd(fd) {}
- io_data::io_data(size_t type,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize)
- :_type(type),_userdata(userdata),_callback(callback),_buffer(buffer),_bufferSize(bufferSize)
+ io_data::io_data(size_t type, int fd,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize)
+ :_type(type),_fd(fd),_userdata(userdata),_callback(callback),_buffer(buffer),_bufferSize(bufferSize)
{
}
@@ -19,6 +19,40 @@ namespace lemon{namespace io{namespace core{
void io_data::call()
{
+ switch(_type)
+ {
+ case LEMON_REACTOR_READ:
+ {
+ ssize_t length = read(_fd, _buffer, _bufferSize);
+
+ if(length == -1)
+ {
+ LEMON_POSIX_ERROR(_errorCode,errno);
+ }
+ else
+ {
+ _numberOfBytesTransferred = length;
+ }
+
+ break;
+ }
+ case LEMON_REACTOR_WRITE:
+ {
+ ssize_t length = write(_fd, _buffer, _bufferSize);
+
+ if(length == -1)
+ {
+ LEMON_POSIX_ERROR(_errorCode,errno);
+ }
+ else
+ {
+ _numberOfBytesTransferred = length;
+ }
+
+ break;
+ }
+ }
+
_callback(_userdata,_numberOfBytesTransferred,&_errorCode);
}
@@ -26,14 +60,13 @@ namespace lemon{namespace io{namespace core{
accept_io_data::accept_io_data
(
- socket * listen,
- socket *peer,
+ int fd,
LemonAcceptCallback callback,
void * userdata,
sockaddr *address,
socklen_t *addressSize
)
- :io_data(LEMON_REACTOR_ACCEPT)
+ :io_data(LEMON_REACTOR_ACCEPT,fd)
{
reset(this,&accept_io_data::callback,NULL,0);
}
@@ -42,15 +75,7 @@ namespace lemon{namespace io{namespace core{
{
accept_io_data * self = (accept_io_data*)userData;
- if(LEMON_SUCCESS(*errorCode))
- {
- self->_callback(userData,reinterpret_cast<LemonIO>(self->_peer),numberOfBytesTransferred,errorCode);
- }
- else
- {
- //TODO: release the socket
- self->_callback(userData,NULL,numberOfBytesTransferred,errorCode);
- }
+ //TODO: call accept function
}
@@ -105,7 +130,14 @@ namespace lemon{namespace io{namespace core{
{
mutex_t::scope_lock lock(_mutex);
- io_data * iodata = alloc_io_data(LEMON_REACTOR_POST_ONE,userdata,callback,buffer,bufferSize);
+ io_data * iodata = alloc_io_data(LEMON_REACTOR_POST_ONE,-1,userdata,callback,buffer,bufferSize);
+
+ post_one(iodata,errorCode);
+ }
+
+ void io_service_reactor::post_one(io_data * iodata,LemonErrorInfo *errorCode)
+ {
+ LEMON_RESET_ERRORINFO(*errorCode);
bool used = false;
@@ -126,11 +158,11 @@ namespace lemon{namespace io{namespace core{
}
}
- io_data * io_service_reactor::alloc_io_data(size_t type,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize)
+ io_data * io_service_reactor::alloc_io_data(size_t type,int fd,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize)
{
mutex_t::scope_lock lock(_iodataAllocatorMutex);
- return new(_iodataAllocator.alloc()) io_data(type,userdata,callback,buffer,bufferSize);
+ return new(_iodataAllocator.alloc()) io_data(type,fd,userdata,callback,buffer,bufferSize);
}
void io_service_reactor::free_io_data(io_data * iodata)
@@ -142,8 +174,7 @@ namespace lemon{namespace io{namespace core{
accept_io_data * io_service_reactor::alloc_io_data
(
- socket * listen,
- socket *peer,
+ int fd,
LemonAcceptCallback callback,
void * userdata,
sockaddr *address,
@@ -152,7 +183,7 @@ namespace lemon{namespace io{namespace core{
{
mutex_t::scope_lock lock(_acceptIODataAllocatorMutex);
- return new(_acceptIODataAllocator.alloc()) accept_io_data(listen,peer,callback,userdata,address,addressSize);
+ return new(_acceptIODataAllocator.alloc()) accept_io_data(fd,callback,userdata,address,addressSize);
}
void io_service_reactor::free_accept_io_data(io_data * iodata)
View
30 io/io_service_reactor.hpp
@@ -30,9 +30,9 @@ namespace lemon{namespace io{namespace core{
{
public:
- io_data(size_t type);
+ io_data(size_t type, int fd);
- io_data(size_t type,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize);
+ io_data(size_t type, int fd,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize);
void reset(void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize);
@@ -52,6 +52,8 @@ namespace lemon{namespace io{namespace core{
size_t _type;
+ int _fd;
+
void *_userdata;
LemonIOCallback _callback;
@@ -73,26 +75,19 @@ namespace lemon{namespace io{namespace core{
accept_io_data
(
- socket * listen,
- socket *peer,
+ int fd,
LemonAcceptCallback callback,
void * userdata,
sockaddr *address,
socklen_t *addressSize
);
- socket * peer() { return _peer; }
-
private:
static void callback(void *userData,size_t numberOfBytesTransferred,const LemonErrorInfo * errorCode);
private:
- socket *_listen;
-
- socket *_peer;
-
LemonAcceptCallback _callback;
void *_userdata;
@@ -114,6 +109,10 @@ namespace lemon{namespace io{namespace core{
typedef memory::ringbuffer::allocator<sizeof(io_data*)> complete_queue;
+ io_service_reactor();
+
+ ~io_service_reactor();
+
void reset() {_exit = false; }
void attach();
@@ -122,22 +121,17 @@ namespace lemon{namespace io{namespace core{
void post_one(LemonIOCallback callback,void * userdata,void * buffer, size_t bufferSize,LemonErrorInfo *errorCode);
- protected:
-
- io_service_reactor();
-
- ~io_service_reactor();
+ void post_one(io_data * iodata,LemonErrorInfo *errorCode);
public:
- io_data * alloc_io_data(size_t type,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize);
+ io_data * alloc_io_data(size_t type,int fd,void * userdata, LemonIOCallback callback, void * buffer, size_t bufferSize);
void free_io_data(io_data * iodata);
accept_io_data * alloc_io_data
(
- socket * listen,
- socket *peer,
+ int fd,
LemonAcceptCallback callback,
void * userdata,
sockaddr *address,
View
44 io/socket.cpp
@@ -3,8 +3,8 @@
#include <lemon/io/io_service_iocp.hpp>
#elif defined(LEMON_IO_KQUEUE)
#include <lemon/io/io_service_kqueue.hpp>
-#else
-#include <lemon/io/io_service_reactor.hpp>
+#elif defined(LEMON_IO_EPOLL)
+#include <lemon/io/io_service_epoll.hpp>
#endif //LEMON_IO_IOCP
#ifndef WIN32
@@ -40,9 +40,11 @@ namespace lemon{namespace io{namespace core{
{
if(SOCKET_ERROR == ::bind(_handle,name,length))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
}
@@ -50,9 +52,11 @@ namespace lemon{namespace io{namespace core{
{
if(SOCKET_ERROR == ::shutdown(_handle,how))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
}
@@ -60,9 +64,11 @@ namespace lemon{namespace io{namespace core{
{
if(SOCKET_ERROR == ::getsockname(_handle,name,bufferSize))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
}
@@ -72,9 +78,11 @@ namespace lemon{namespace io{namespace core{
if(SOCKET_ERROR == sendSize)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
return sendSize;
@@ -86,9 +94,11 @@ namespace lemon{namespace io{namespace core{
if(SOCKET_ERROR == sendSize)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
return sendSize;
@@ -100,9 +110,11 @@ namespace lemon{namespace io{namespace core{
if(SOCKET_ERROR == sendSize)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
return sendSize;
@@ -114,9 +126,11 @@ namespace lemon{namespace io{namespace core{
if(SOCKET_ERROR == sendSize)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
return sendSize;
@@ -126,9 +140,11 @@ namespace lemon{namespace io{namespace core{
{
if(SOCKET_ERROR == ::connect(_handle,addr,addrlen))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
}
@@ -136,9 +152,11 @@ namespace lemon{namespace io{namespace core{
{
if(SOCKET_ERROR == ::listen(_handle,backlog))
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
}
@@ -148,9 +166,11 @@ namespace lemon{namespace io{namespace core{
if(INVALID_SOCKET == handle)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_SOCKET_ERROR(errorCode);
+
+ errorCode.check_throw();
}
return service()->create_socket(_af,_type,_protocol,handle);
View
79 io/socket_reactor.cpp
@@ -1,18 +1,95 @@
+#include <errno.h>
#include <lemon/io/socket_reactor.hpp>
+#ifndef LEMON_IO_IOCP
+
+#ifdef LEMON_HAS_FCNTL_H
+# include <fcntl.h>
+#else
+# error "not support platform"
+#endif
+
namespace lemon{namespace io{namespace core{
+ int __setnonblocking(int sockfd)
+ {
+ return fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK);
+ }
+
socket::socket(int af, int type, int protocol,io_service * service)
:socket_base(af,type,protocol,::socket(af,type,protocol),service)
{
+ try
+ {
+ if(handle() == -1)
+ {
+ error_info errorCode;
+
+ LEMON_POSIX_ERROR(errorCode,errno);
+
+ errorCode.check_throw();
+ }
+ // if(__setnonblocking(handle()) < 0)
+ // {
+ // error_info errorCode;
+
+ // LEMON_POSIX_ERROR(errorCode,errno);
+
+ // errorCode.check_throw();
+ // }
+ }
+ catch(...)
+ {
+ release();
+
+ throw;
+ }
}
socket::socket(int af, int type, int protocol,LemonNativeSock sock, io_service * service)
:socket_base(af,type,protocol,sock,service)
{
+ // if(__setnonblocking(handle()) < 0)
+ // {
+ // error_info errorCode;
+
+ // LEMON_POSIX_ERROR(errorCode,errno);
+
+ // errorCode.check_throw();
+ // }
+ }
+
+ void socket::async_send(const byte_t * buffer, size_t length, int flag,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
+
+ }
+
+ void socket::async_sendto(const byte_t * buffer, size_t length, int flag,const sockaddr * addr, socklen_t addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
+
+ }
+
+ void socket::async_receive(byte_t * buffer, size_t length, int flag,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
+
+ }
+
+ void socket::async_recvfrom(byte_t * buffer, size_t length, int flag,sockaddr * addr,socklen_t * addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
+
+ }
+
+ void socket::async_connect(const sockaddr * addr, socklen_t addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
+
+ }
+
+ void socket::async_accept(sockaddr * addr,socklen_t * addrlen,LemonAcceptCallback callback, void * userdata,LemonErrorInfo *errorCode)
+ {
}
+}}}
-}}}
+#endif //LEMON_IO_IOCP
View
16 io/socket_reactor.hpp
@@ -23,6 +23,22 @@ namespace lemon{namespace io{namespace core{
socket(int af, int type, int protocol,io_service * service);
socket(int af, int type, int protocol,LemonNativeSock sock, io_service * service);
+
+ public:
+
+ // the async api
+
+ void async_send(const byte_t * buffer, size_t length, int flag,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode);
+
+ void async_sendto(const byte_t * buffer, size_t length, int flag,const sockaddr * addr, socklen_t addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode);
+
+ void async_receive(byte_t * buffer, size_t length, int flag,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode);
+
+ void async_recvfrom(byte_t * buffer, size_t length, int flag,sockaddr * addr,socklen_t * addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode);
+
+ void async_connect(const sockaddr * addr, socklen_t addrlen,LemonIOCallback callback, void * userdata,LemonErrorInfo *errorCode);
+
+ void async_accept(sockaddr * addr,socklen_t * addrlen,LemonAcceptCallback callback, void * userdata,LemonErrorInfo *errorCode);
};
}}}
View
4 io/unix.cmake
@@ -3,3 +3,7 @@ include(CheckIncludeFiles)
check_include_files(sys/epoll.h LEMON_IO_EPOLL)
check_include_files(sys/event.h LEMON_IO_KQUEUE)
+
+check_include_files(fcntl.h LEMON_HAS_FCNTL_H)
+
+check_include_files(sys/eventfd.h LEMON_HAS_EVENTFD_H)
View
4 sys/net.cpp
@@ -402,13 +402,15 @@ LEMON_SYS_API
LEMON_ALLOC_HANDLE(LemonResovler,resolver);
- int ec = ::getaddrinfo(nodeName,serverName,NULL,&resolver->handle);
+ int ec = ::getaddrinfo(nodeName,serverName,0,&resolver->handle);
if(0 != ec){
#ifdef WIN32
LEMON_WIN32_ERROR(*errorCode,WSAGetLastError());
#else
LEMON_POSIX_ERROR(*errorCode,ec);
+
+ perror(gai_strerror(ec));
#endif //UNIX
LEMON_FREE_HANDLE(resolver);
View
4 trace/message.cpp
@@ -207,9 +207,11 @@ namespace lemon{namespace trace{
if(_length > LEMON_TRACE_MESSAGE_MAX_LENGTH)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_USER_ERROR(errorCode,LEMON_TRACE_BINARY_MESSAGE_FORMAT_ERROR);
+
+ errorCode.check_throw();
}
result += reader.read((byte_t*)_buffer,_length);
View
12 trace/object.cpp
@@ -38,9 +38,11 @@ namespace lemon{namespace trace{
{
//TODO: add the pipe protocol
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_USER_ERROR(errorCode,LEMON_TRACE_INVALID_URL);
+
+ errorCode.check_throw();
}
start = 6;
@@ -50,9 +52,11 @@ namespace lemon{namespace trace{
if(URL.npos ==position)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_USER_ERROR(errorCode,LEMON_TRACE_INVALID_URL);
+
+ errorCode.check_throw();
}
std::string nodename = URL.substr(start,position - start);
@@ -63,9 +67,11 @@ namespace lemon{namespace trace{
if(net::resolver_iterator() == iter)
{
- scope_error_info errorCode;
+ error_info errorCode;
LEMON_USER_ERROR(errorCode,LEMON_TRACE_INVALID_URL);
+
+ errorCode.check_throw();
}
return NULL;
Please sign in to comment.
Something went wrong with that request. Please try again.