「博文」查找并修复Hyper应用中的内存泄漏
作者对其应用的下一个版本进行负载测试,使用wrk工具发送请求几秒之后,后端内存占用了1.3GB,内存占用的增长速度大约每秒50MB。
初步调试:
开始使用heaptrack工具,发现内存只分配了8KB,问题出在了heaptrack工具和jemalloc并不搭,所以换用Rust的system分配器。
use std::alloc::System;
#[global_allocator]
static A: System = System;
这样就可以用heaptrack来检测到内存泄漏了。然而还是没有得到什么有用的信息。
改变策略:
有一个办法,可以通过逐步删除部分程序来确定内存泄漏的位置。那么有没有这样一个工具来代替开发者手工完成?C-Reduce行不行?可惜的是,他们发现C-Reduce无法支持Rust。所以只能想办法徒手实现了一个最小版本的应用示例。
#![feature(alloc_system)]
#![feature(allocator_api)]
#[global_allocator]
static GLOBAL: std::alloc::System = std::alloc::System;
#[macro_use]
extern crate failure;
extern crate future_utils;
extern crate hyper;
extern crate tokio;
use failure::Error;
use future_utils::{BoxSendFuture, FutureExt};
use hyper::service::Service;
use hyper::Body;
use std::time::Duration;
use tokio::prelude::*;
fn main() {
let server = hyper::Server::bind(&"127.0.0.1:31337".parse().unwrap())
.serve(|| Ok::<_, Box<std::error::Error + Send + Sync>>(Server));
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.spawn(server.map_err(move |_err| {}));
println!("server started");
::std::thread::sleep(Duration::from_secs(10));
println!("shutting down");
rt.shutdown_now().wait().unwrap();
}
#[derive(Debug, Fail)]
#[fail(display = "myfail")]
pub struct MyFail;
pub struct Server;
impl Service for Server {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
type Future = BoxSendFuture<hyper::Response<Self::ResBody>, Self::Error>;
fn call(&mut self, _request: hyper::Request<Self::ReqBody>) -> Self::Future {
Err(Error::from(::MyFail))
.into_future()
.or_else(|err: Error| Ok(err.downcast::<::MyFail>().unwrap().into_future()) // removing this gets rid of the leak
.map(|_| ::hyper::Response::new(Body::empty()))
.into_send_boxed()
}
}
最终发现,是在转换错误类型的时候调用into_future()
的时候发生了泄漏。然后他们使用cargo-geiger工具,去发现导致这次泄漏的unsafe代码。
最终他们定位到了问题所在:
pub(crate) fn downcast<T: Fail>(self) -> Result<T, ErrorImpl> {
let ret: Option<T> = self.failure().downcast_ref().map(|fail| {
unsafe {
// drop the backtrace
let _ = ptr::read(&self.inner.backtrace as *const Backtrace);
// read out the fail type
ptr::read(fail as *const T)
}
});
match ret {
Some(ret) => {
// forget self (backtrace is dropped, failure is moved
mem::forget(self);
Ok(ret)
}
_ => Err(self)
}
}
是failure库里的downcast
方法使用了mem::forget
。这就导致了Error::downcast
并没有把Box<Inner<Fail>>
给drop掉,而仅仅是drop了backtrace。
之前有一个PR补丁修复了此泄漏漏洞 failure-pr-262,
// deallocate the box without dropping the inner parts
#[cfg(has_global_alloc)] {
use std::alloc::{dealloc, Layout};
unsafe {
let layout = Layout::for_value(&*self.inner);
let ptr = Box::into_raw(self.inner);
dealloc(ptr as *mut u8, layout);
}
}
但是在Rust 1.18之前的版本还存在泄漏问题,可见作者用的还是老版本。
因此,他的同事给Failure提交了一个PR,修复此问题,现在已经被合并到failure中。
pub(crate) fn downcast<T: Fail>(self) -> Result<T, ErrorImpl> {
if self.failure().__private_get_type_id__() == TypeId::of::<T>() {
let ErrorImpl { inner } = self;
let casted = unsafe { Box::from_raw(Box::into_raw(inner) as *mut Inner<T>) };
let Inner { backtrace:_, failure } = *casted;
Ok(failure)
} else {
Err(self)
}
}
「项目」sled 发布 0.16版本
关键字:[embedded, database, concurrent, b-tree, zero-copy]
sled是一款Rust实现的现代化嵌入式数据库
新的版本更加可靠,且实现了零Copy读取,Rust的所有权模型让引用安全地返回给缓存而无需防御性的拷贝。
「GUI框架」Azul:面向IMGUI的免费功能性GUI框架
关键字: [azul, gui, imgui, webrender, mvvm]
支持用Rust编写桌面软件,基于Mozilla WebRender渲染引擎。
目前还在完善中,期望特点(也就是说,当前这些功能并非全部支持):
- 跨平台
- IMGUI (Immediate Mode GUI )/ MVVM 编程模型
- 基于Dom的无状态组件
- CSS风格的样式引擎,支持多种通用CSS属性
- 基于Flexbox的布局
- 内置标准控件
- 自定义widgets
- SVG渲染引擎,2D绘图助手 (lines, circles, rects, etc.)
- OpenGL集成
- Async I/O
- 可选的集成日志和错误报告辅助
- 独立二进制部署,最小二进制大小(5MB)
- CPU使用率(0-4%),内存使用率(总共约50MB)
- 快速重绘时间(0.5~4ms),包括了高效的状态缓存
「异步Web框架」Gotham 0.3发布
关键字: [Gotham, async, web-framwork, tokio, hyper]
特点:
- 基于新的tokio库
- 采用hyper 0.12和新的http库
- 内部路由算法改进
- 自定义Gotham使用tokio运行时
- 通过tokio-fs提供异步静态文件
- 更多性能和可用性改进
「项目」RESSA:Rusty ECMAScript语法分析器
关键字: [RESSA, ECMAScript, JavaScript, parse]
该项目是用于把js文本解析为基于ESTREE的AST
「Web框架」使用actix-web制作一个验证用户身份的微服务 Part 2
关键字: [actix-web, microservice, diesel, auth]
「博文」如何使用Cargo构建Qt应用
关键字: [Qt, cargo]
目的是想让Qt的安装和运行更加简单
每日新闻订阅地址:
欢迎通过GitHub issues投稿。
评论区
写评论还没有评论