对一次OOM bug的反思及std::function与move-only可调用对象之间的思考
前言在编写程序时,很少触发OOM(out of memory) bug,一般是因为程序中存在逻辑错误或者一些马虎的地方写错了。而这次触发了OOM正是因为我自己马虎了,序列化函数的缓冲参数声明类型不是引用且支持拷贝,导致并没有将内容正确写入,然后另一端读取到错误的字段,该字段用于确定std::vector<char>的大小,调用resize()分配了超过当前剩余物理内存,从而触发OOM killer将进程终止了。1234567891011// buffer passed by valueinline void SerializeField(std::vector<char> const &buf, ChunkList buffer) { buffer.Append32(buf.size()); buffer.Append(buf.data(), buf.size());}inline void SetField(std::vector<char> &buf, Buffer &buffer) { ...
C++11实现C++14捕获列表变量初始化
前言C++11引入了lambda表达式,让C++的<functional>,<algorithm>的使用更方便和具有可读性:12345678910111213141516171819202122232425#include <algorithm>struct KeyValue { int key; int value;};std::vector<KeyValue> kvs{...};// Before C++11inline bool KvCmp(KeyValue const &x, KeyValue const &y) throw() { return x.key < y.key;}// ORstruct KvCmp { inline bool operator()(KeyValue const &x, KeyValue const &y) throw() { return x.key < y.key ...
对于引用语义和值语义以及移动语义的思考
前言曾经在关于noncopyable和STL容器的思考中讨论了一些关于noncopyable的话题。在那篇文章关于什么样的类才应当是不可拷贝的讲太粗糙了。这里我想进一步讨论语义以及符合什么条件的类适合是copyable或moveable。
值语义值语义(value semantic)关注的是对象的值本身,至于是不是同一个对象,它并不关注,因此往往这类对象需要支持拷贝(duplicate),它拥有与原来对象相同的值,却是一个新的对象(从memory角度来看,拥有一个区别于原来对象的独特地址)。典型的拥有值语义的对象就是各种数据结构,比如STL中的容器。C++是如何体现这种语义的,即拷贝是如何提供的?C++有一类特殊成员函数是与拷贝相关的,即拷贝构造函数(copy constructor)和拷贝赋值运算符重载(copy assignment overloading operator),它们提供隐式的拷贝操作,即不需要显式调用这些函数,编译器通过特定的语法可以转调用这些函数。某种意义上,既省了功夫也提高了可读性。但是代价是隐式本身是个双刃剑,有的时候因为自己的不注意,导致出现了没必要的拷贝, ...
mmkv分布式架构设想
受《DDIA》中“Request Routing”一节的启发,我提出两种架构以支持mmkv提供分布式服务。以下称每个提供实际服务的服务器为一个结点(node),结点的集合为一个集群(cluster)。
Client router每个结点都保存着相同的服务相关的信息(即元数据),客户端可以连接集群中的任一个结点,然后获取初始版本的服务元数据,客户端可以自己决定如何路由请求到指定的结点获取服务。当集群有新的结点加入或退出,所有结点需要更新元数据并保持一致,然后客户端发送新请求到原来路由的结点时,该结点回应重定向到新的结点,更新客户端的元数据。另一种可能的做法是结点将新的元数据发送给所有建立连接的客户端要求它们同步更新,但这样做IO开销可能很大,并且如果很多客户端不再发新请求的话,那么很多同步是没必要的,因此不采用。假设结点个数为N,每个结点互相建立连接,需要建立N(N-1)个连接。通过一致性协议保证各个结点之间的元数据一致,同时也保证集群的容错性和可用性。
Independent router-tier在客户端和集群之间增加一个服务器作为路由器(router),客户端和结点不需要维护任何服 ...
Kanon Poller
d92481fdbb82ca6e6887b74613b17a412b818b62f6b4b433626156601fb1c42b91bf9f3231e15e3a5c1b426d1ec341aa83e996bb26a1aa125c49bfe8c37e9e6a8754411f8b3f97dc24b7cd17390a572a7c3b20b4646c813b9c8aceede17c7c07c3abb1c225db0ebb5f689acf72f1ae7ada99dbedb80503da67b40664b0f2237deff157e2f23710a0dc6b51f5a89696efcc2e0647fc2fb82e79a5bf458b93fc57a622a0af5393652c9a7fee55cba95aee76172cd40e8c104970c396bbceaf768c5a686e338acadde9d810f5386fd527fc0901e6043e95ee37dd6e7dac3c62c063b024439b33b0826245c6c431ec6fcd6f68c5bf47a3f04e4a7 ...
Kanon EPoller
Kanon(六): Epoller(epoll wrapper)谈谈Epoller的实现细节。
UpdateChannel根据man epoll可以知道对内核的事件表的操作是通过epoll_ctl()和其参数指定的:
EPOLL_ADD
EPOLL_MOD
EPOLL_DEL
因此封装成:123void UpdateChannel(Channel* ch);// Forward to UpdateEpollEvent()void UpdateEpollEvent(int op, Channel* ch);对于epoll_ctl(),它只需要fd,因此Channel本身是不兼容的,它需要额外存储,为了避免多余的空间开销,epoll它并不是返回fd和触发事件,而是返回epoll_data_t数据和触发事件,具体来说,是这样的:
12345678910111213typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64;} epoll_data_t;struct epoll ...
Kanon PollerBase
Kanon(五): PollerBase(IO解多路复用器)
PollerBase在Reactor中充当IO解多路复用器(I/O Demultiplexer)。
Q:为什么是IO解多路复用器,用到的API常称为IO多路复用吗?A: 所谓IO多路复用是复用监视fd集合,有多个源(source),当其中有事件触发,对应的fd转变为活跃状态,就会形成对应的活跃fd集合(Output),我们得到的就是活跃fd的集合(具体获取方式各API有别),然后分发,这就是IO解多路复用。
相信读者知道:select()/poll()/epoll_wait()本质都是同步API,而阻塞与非阻塞取决于timeout实参,如果指定为负数,就是阻塞的,其他的就是非阻塞,只不过有定时。该性质也可以用于实现定时器操作,不过精度是毫秒(ms),相比timerfd而言要更低。
kanon支持两种IO多路复用API(—表示抽象为):
poll() — Poller
epoll_wait() — Epoller均继承自PollerBase。
PollerBase是一个抽象基类,并且没有任何数据成员,因此是接口类,即只 ...
Kanon Channel
Kanon(四): Channel(事件分发器)
Channel是Reactor模式中的事件分发器(Event Dispatcher),管理感兴趣的事件注册注销并根据触发的事件分发到对应的事件处理器上。
事件可以分为:
读事件(Read event)
写事件(Write event)
错误事件(error event)
关闭事件(close event)
1234void SetReadCallback(ReadEventCallback);void SetWriteCallback(EventCallback);void SetErrorCallback(EventCallback);void SetCloseCallback(EventCallback);
但是实际上用户感兴趣的只有前两者,后两者由库设置事件处理器。因此事件接口为:12345678void EnableReading();void EnableWriting();void DisableReading();void DisableWriting();void DisableAll();bool IsReadin ...
Kanon EventLoopThread
Kanon(三.一): EventLoopThread(EventLoop wrapper)
Kanon EventLoop
Kanon(三):深入EventLoop
在Reactor中聊到了事件循环,但是没有深入了解其构成,大体来说:
(Private)提供注册注销Channel的API(转发给poller_成员
(Private)提供判断是否与初始化时绑定的线程一致的API以及断言API(保证One loop per thread)
提供启动和退出事件循环API
提供添加异步调用API(第三阶段)
提供定时器的注册注销API
保证One loop per thread如何判断各种调用是不是在绑定的事件循环初始化时所在的线程上是维护One loop per thread不变式必要的功能。如果TcpConnection的各种调用最终是在不同的线程上运行的,会带来线程安全问题以及没有充分利用多核,因此这是必须避免的。具体来说事件循环在初始化的时候会绑定对应的线程ID(tid),这是个TLS(Thread local storage)变量,所以在不同线程看来是不一样的。123456789101112131415161718EventLoop::EventLoop() : owner_thread_id_ ...