mirror of
https://github.com/ossrs/srs.git
synced 2025-02-13 11:51:57 +00:00
Merge branch '2.0release' into develop
This commit is contained in:
commit
00d1fdded0
24 changed files with 285 additions and 64 deletions
|
@ -19,9 +19,6 @@ echo "SRS_JOBS: ${SRS_JOBS}"
|
|||
mkdir -p ${ff_build_dir}
|
||||
mkdir -p ${ff_release_dir}
|
||||
|
||||
# for ubuntu14, donot compile libaacplus.
|
||||
UBUNTU14=NO;grep -in "Ubuntu 14." /etc/issue >/dev/null 2>&1; if [[ $? -eq 0 ]]; then UBUNTU14=YES; fi
|
||||
|
||||
# yasm for libx264
|
||||
ff_yasm_bin=${ff_release_dir}/bin/yasm
|
||||
if [[ -f ${ff_yasm_bin} ]]; then
|
||||
|
|
2
trunk/configure
vendored
2
trunk/configure
vendored
|
@ -138,7 +138,7 @@ if [ $SRS_MIPS_UBUNTU12 = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lgcc_eh
|
|||
MODULE_ID="CORE"
|
||||
MODULE_DEPENDS=()
|
||||
ModuleLibIncs=(${SRS_OBJS_DIR})
|
||||
MODULE_FILES=("srs_core" "srs_core_autofree" "srs_core_performance")
|
||||
MODULE_FILES=("srs_core" "srs_core_autofree" "srs_core_performance" "srs_core_mem_watch")
|
||||
CORE_INCS="src/core"; MODULE_DIR=${CORE_INCS} . auto/modules.sh
|
||||
CORE_OBJS="${MODULE_OBJS[@]}"
|
||||
#
|
||||
|
|
|
@ -16,6 +16,8 @@ file
|
|||
../../src/core/srs_core.cpp,
|
||||
../../src/core/srs_core_autofree.hpp,
|
||||
../../src/core/srs_core_autofree.cpp,
|
||||
../../src/core/srs_core_mem_watch.hpp,
|
||||
../../src/core/srs_core_mem_watch.cpp,
|
||||
../../src/core/srs_core_performance.hpp,
|
||||
../../src/core/srs_core_performance.cpp,
|
||||
kernel readonly separator,
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
<ClInclude Include="..\..\src\app\srs_app_utility.hpp" />
|
||||
<ClInclude Include="..\..\src\core\srs_core.hpp" />
|
||||
<ClInclude Include="..\..\src\core\srs_core_autofree.hpp" />
|
||||
<ClInclude Include="..\..\src\core\srs_core_mem_watch.hpp" />
|
||||
<ClInclude Include="..\..\src\core\srs_core_performance.hpp" />
|
||||
<ClInclude Include="..\..\src\kernel\srs_kernel_aac.hpp" />
|
||||
<ClInclude Include="..\..\src\kernel\srs_kernel_avc.hpp" />
|
||||
|
@ -181,6 +182,7 @@
|
|||
<ClCompile Include="..\..\src\app\srs_app_utility.cpp" />
|
||||
<ClCompile Include="..\..\src\core\srs_core.cpp" />
|
||||
<ClCompile Include="..\..\src\core\srs_core_autofree.cpp" />
|
||||
<ClCompile Include="..\..\src\core\srs_core_mem_watch.cpp" />
|
||||
<ClCompile Include="..\..\src\core\srs_core_performance.cpp" />
|
||||
<ClCompile Include="..\..\src\kernel\srs_kernel_aac.cpp" />
|
||||
<ClCompile Include="..\..\src\kernel\srs_kernel_avc.cpp" />
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
3C36DB5B1ABD1CB90066CCAF /* srs_lib_bandwidth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */; };
|
||||
3C36DB5C1ABD1CB90066CCAF /* srs_lib_simple_socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB571ABD1CB90066CCAF /* srs_lib_simple_socket.cpp */; };
|
||||
3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */; };
|
||||
3C5265B41B241BF0009CA186 /* srs_core_mem_watch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */; };
|
||||
3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */; };
|
||||
3C663F101AB0155100286D8B /* srs_audio_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */; };
|
||||
3C663F111AB0155100286D8B /* srs_bandwidth_check.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F041AB0155100286D8B /* srs_bandwidth_check.c */; };
|
||||
|
@ -327,6 +328,8 @@
|
|||
3C36DB581ABD1CB90066CCAF /* srs_lib_simple_socket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_lib_simple_socket.hpp; path = ../../../src/libs/srs_lib_simple_socket.hpp; sourceTree = "<group>"; };
|
||||
3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_librtmp.cpp; path = ../../../src/libs/srs_librtmp.cpp; sourceTree = "<group>"; };
|
||||
3C36DB5A1ABD1CB90066CCAF /* srs_librtmp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_librtmp.hpp; path = ../../../src/libs/srs_librtmp.hpp; sourceTree = "<group>"; };
|
||||
3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core_mem_watch.cpp; path = ../../../src/core/srs_core_mem_watch.cpp; sourceTree = "<group>"; };
|
||||
3C5265B31B241BF0009CA186 /* srs_core_mem_watch.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_core_mem_watch.hpp; path = ../../../src/core/srs_core_mem_watch.hpp; sourceTree = "<group>"; };
|
||||
3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_aac_raw_publish.c; path = ../../../research/librtmp/srs_aac_raw_publish.c; sourceTree = "<group>"; };
|
||||
3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_audio_raw_publish.c; path = ../../../research/librtmp/srs_audio_raw_publish.c; sourceTree = "<group>"; };
|
||||
3C663F041AB0155100286D8B /* srs_bandwidth_check.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_bandwidth_check.c; path = ../../../research/librtmp/srs_bandwidth_check.c; sourceTree = "<group>"; };
|
||||
|
@ -428,6 +431,8 @@
|
|||
children = (
|
||||
3C1231F01AAE652C00CE8F6C /* srs_core_autofree.cpp */,
|
||||
3C1231F11AAE652C00CE8F6C /* srs_core_autofree.hpp */,
|
||||
3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */,
|
||||
3C5265B31B241BF0009CA186 /* srs_core_mem_watch.hpp */,
|
||||
3C1231F21AAE652C00CE8F6C /* srs_core_performance.cpp */,
|
||||
3C1231F31AAE652C00CE8F6C /* srs_core_performance.hpp */,
|
||||
3C1231F41AAE652D00CE8F6C /* srs_core.cpp */,
|
||||
|
@ -882,6 +887,7 @@
|
|||
3CC52DDC1ACE4023006FEB01 /* srs_utest_protocol.cpp in Sources */,
|
||||
3C663F151AB0155100286D8B /* srs_h264_raw_publish.c in Sources */,
|
||||
3C1231F61AAE652D00CE8F6C /* srs_core_autofree.cpp in Sources */,
|
||||
3C5265B41B241BF0009CA186 /* srs_core_mem_watch.cpp in Sources */,
|
||||
3C1EE6D71AB1367D00576EE9 /* README.md in Sources */,
|
||||
3C1232411AAE81A400CE8F6C /* srs_raw_avc.cpp in Sources */,
|
||||
3C1232491AAE81A400CE8F6C /* srs_rtmp_utility.cpp in Sources */,
|
||||
|
|
|
@ -2860,13 +2860,15 @@ vector<string> SrsConfig::get_engine_vfilter(SrsConfDirective* engine)
|
|||
|
||||
string SrsConfig::get_engine_vcodec(SrsConfDirective* engine)
|
||||
{
|
||||
static string DEFAULT = "";
|
||||
|
||||
if (!engine) {
|
||||
return "";
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = engine->get("vcodec");
|
||||
if (!conf) {
|
||||
return "";
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
return conf->arg0();
|
||||
|
@ -2998,13 +3000,15 @@ vector<string> SrsConfig::get_engine_vparams(SrsConfDirective* engine)
|
|||
|
||||
string SrsConfig::get_engine_acodec(SrsConfDirective* engine)
|
||||
{
|
||||
static string DEFAULT = "";
|
||||
|
||||
if (!engine) {
|
||||
return "";
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = engine->get("acodec");
|
||||
if (!conf) {
|
||||
return "";
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
return conf->arg0();
|
||||
|
|
|
@ -783,7 +783,7 @@ public:
|
|||
virtual std::vector<std::string> get_engine_vfilter(SrsConfDirective* engine);
|
||||
/**
|
||||
* get the vcodec of engine,
|
||||
* the codec of video, copy or libx264
|
||||
* the codec of video, can be vn, copy or libx264
|
||||
*/
|
||||
virtual std::string get_engine_vcodec(SrsConfDirective* engine);
|
||||
/**
|
||||
|
@ -827,7 +827,7 @@ public:
|
|||
virtual std::vector<std::string> get_engine_vparams(SrsConfDirective* engine);
|
||||
/**
|
||||
* get the acodec of engine,
|
||||
* the audio codec can be copy or libaacplus
|
||||
* the audio codec can be an, copy or libfdk_aac
|
||||
*/
|
||||
virtual std::string get_engine_acodec(SrsConfDirective* engine);
|
||||
/**
|
||||
|
|
|
@ -40,6 +40,7 @@ SrsConnection::SrsConnection(IConnectionManager* cm, st_netfd_t c)
|
|||
id = 0;
|
||||
manager = cm;
|
||||
stfd = c;
|
||||
disposed = false;
|
||||
|
||||
// the client thread should reap itself,
|
||||
// so we never use joinable.
|
||||
|
@ -50,12 +51,24 @@ SrsConnection::SrsConnection(IConnectionManager* cm, st_netfd_t c)
|
|||
|
||||
SrsConnection::~SrsConnection()
|
||||
{
|
||||
dispose();
|
||||
|
||||
srs_freep(pthread);
|
||||
}
|
||||
|
||||
void SrsConnection::dispose()
|
||||
{
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
|
||||
/**
|
||||
* when delete the connection, stop the connection,
|
||||
* close the underlayer socket, delete the thread.
|
||||
*/
|
||||
srs_close_stfd(stfd);
|
||||
srs_freep(pthread);
|
||||
}
|
||||
|
||||
int SrsConnection::start()
|
||||
|
|
|
@ -83,10 +83,19 @@ protected:
|
|||
* the ip of client.
|
||||
*/
|
||||
std::string ip;
|
||||
/**
|
||||
* whether the connection is disposed,
|
||||
* when disposed, connection should stop cycle and cleanup itself.
|
||||
*/;
|
||||
bool disposed;
|
||||
public:
|
||||
SrsConnection(IConnectionManager* cm, st_netfd_t c);
|
||||
virtual ~SrsConnection();
|
||||
public:
|
||||
/**
|
||||
* to dipose the connection.
|
||||
*/
|
||||
virtual void dispose();
|
||||
/**
|
||||
* start the client green thread.
|
||||
* when server get a client from listener,
|
||||
|
|
|
@ -48,6 +48,7 @@ using namespace std;
|
|||
// for example, libaacplus, aac, fdkaac
|
||||
#define SRS_RTMP_ENCODER_ACODEC "aac"
|
||||
#define SRS_RTMP_ENCODER_LIBAACPLUS "libaacplus"
|
||||
#define SRS_RTMP_ENCODER_LIBFDKAAC "libfdk_aac"
|
||||
|
||||
SrsFFMPEG::SrsFFMPEG(std::string ffmpeg_bin)
|
||||
{
|
||||
|
@ -174,7 +175,7 @@ int SrsFFMPEG::initialize_transcode(SrsConfDirective* engine)
|
|||
}
|
||||
|
||||
// @see, https://github.com/simple-rtmp-server/srs/issues/145
|
||||
if (acodec == SRS_RTMP_ENCODER_LIBAACPLUS) {
|
||||
if (acodec == SRS_RTMP_ENCODER_LIBAACPLUS && acodec != SRS_RTMP_ENCODER_LIBFDKAAC) {
|
||||
if (abitrate < 16 || abitrate > 72) {
|
||||
ret = ERROR_ENCODER_ABITRATE;
|
||||
srs_error("invalid abitrate for aac: %d, must in [16, 72], ret=%d", abitrate, ret);
|
||||
|
|
|
@ -529,7 +529,7 @@ int SrsHttpApi::do_cycle()
|
|||
skt.set_recv_timeout(SRS_HTTP_RECV_TIMEOUT_US);
|
||||
|
||||
// process http messages.
|
||||
for (;;) {
|
||||
while(!disposed) {
|
||||
ISrsHttpMessage* req = NULL;
|
||||
|
||||
// get a http message
|
||||
|
|
|
@ -2533,7 +2533,7 @@ int SrsHttpConn::do_cycle()
|
|||
skt.set_recv_timeout(SRS_HTTP_RECV_TIMEOUT_US);
|
||||
|
||||
// process http messages.
|
||||
for (;;) {
|
||||
while (!disposed) {
|
||||
ISrsHttpMessage* req = NULL;
|
||||
|
||||
// get a http message
|
||||
|
|
|
@ -320,11 +320,14 @@ int SrsRtmpConn::service_cycle()
|
|||
}
|
||||
srs_verbose("on_bw_done success");
|
||||
|
||||
while (true) {
|
||||
while (!disposed) {
|
||||
ret = stream_service_cycle();
|
||||
|
||||
// stream service must terminated with error, never success.
|
||||
srs_assert(ret != ERROR_SUCCESS);
|
||||
// when terminated with success, it's user required to stop.
|
||||
if (ret == ERROR_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// when not system control error, fatal error, return.
|
||||
if (!srs_is_system_control_error(ret)) {
|
||||
|
@ -361,6 +364,8 @@ int SrsRtmpConn::service_cycle()
|
|||
srs_error("control message(%d) reject as error. ret=%d", ret, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsRtmpConn::stream_service_cycle()
|
||||
|
@ -635,7 +640,7 @@ int SrsRtmpConn::do_playing(SrsSource* source, SrsConsumer* consumer, SrsQueueRe
|
|||
// set the sock options.
|
||||
play_set_sock_options();
|
||||
|
||||
while (true) {
|
||||
while (!disposed) {
|
||||
// collect elapse for pithy print.
|
||||
pprint->elapse();
|
||||
|
||||
|
@ -865,7 +870,7 @@ int SrsRtmpConn::do_publishing(SrsSource* source, SrsPublishRecvThread* trd)
|
|||
}
|
||||
|
||||
int64_t nb_msgs = 0;
|
||||
while (true) {
|
||||
while (!disposed) {
|
||||
pprint->elapse();
|
||||
|
||||
// cond wait for error.
|
||||
|
|
|
@ -47,6 +47,7 @@ using namespace std;
|
|||
#include <srs_app_rtsp.hpp>
|
||||
#include <srs_app_statistic.hpp>
|
||||
#include <srs_app_caster_flv.hpp>
|
||||
#include <srs_core_mem_watch.hpp>
|
||||
|
||||
// signal defines.
|
||||
#define SIGNAL_RELOAD SIGHUP
|
||||
|
@ -513,15 +514,7 @@ void SrsServer::destroy()
|
|||
{
|
||||
srs_warn("start destroy server");
|
||||
|
||||
_srs_config->unsubscribe(this);
|
||||
|
||||
close_listeners(SrsListenerRtmpStream);
|
||||
close_listeners(SrsListenerHttpApi);
|
||||
close_listeners(SrsListenerHttpStream);
|
||||
|
||||
#ifdef SRS_AUTO_INGEST
|
||||
ingester->dispose();
|
||||
#endif
|
||||
dispose();
|
||||
|
||||
#ifdef SRS_AUTO_HTTP_API
|
||||
srs_freep(http_api_mux);
|
||||
|
@ -547,28 +540,39 @@ void SrsServer::destroy()
|
|||
srs_freep(signal_manager);
|
||||
|
||||
srs_freep(handler);
|
||||
|
||||
// @remark never destroy the connections,
|
||||
// for it's still alive.
|
||||
|
||||
// @remark never destroy the source,
|
||||
// when we free all sources, the fmle publish may retry
|
||||
// and segment fault.
|
||||
}
|
||||
|
||||
void SrsServer::dispose()
|
||||
{
|
||||
_srs_config->unsubscribe(this);
|
||||
|
||||
// prevent fresh clients.
|
||||
close_listeners(SrsListenerRtmpStream);
|
||||
close_listeners(SrsListenerHttpApi);
|
||||
close_listeners(SrsListenerHttpStream);
|
||||
close_listeners(SrsListenerMpegTsOverUdp);
|
||||
close_listeners(SrsListenerRtsp);
|
||||
close_listeners(SrsListenerFlv);
|
||||
|
||||
#ifdef SRS_AUTO_INGEST
|
||||
ingester->dispose();
|
||||
srs_trace("gracefully dispose ingesters");
|
||||
#endif
|
||||
|
||||
SrsSource::dispose_all();
|
||||
srs_trace("gracefully dispose sources");
|
||||
|
||||
srs_trace("terminate server");
|
||||
while (!conns.empty()) {
|
||||
std::vector<SrsConnection*>::iterator it;
|
||||
for (it = conns.begin(); it != conns.end(); ++it) {
|
||||
SrsConnection* conn = *it;
|
||||
conn->dispose();
|
||||
}
|
||||
|
||||
st_usleep(100 * 1000);
|
||||
}
|
||||
|
||||
#ifdef SRS_MEM_WATCH
|
||||
srs_memory_report();
|
||||
#endif
|
||||
}
|
||||
|
||||
int SrsServer::initialize(ISrsServerCycle* cycle_handler)
|
||||
|
@ -889,6 +893,9 @@ void SrsServer::on_signal(int signo)
|
|||
signal_gmc_stop = true;
|
||||
#else
|
||||
srs_trace("user terminate program");
|
||||
#ifdef SRS_MEM_WATCH
|
||||
srs_memory_report();
|
||||
#endif
|
||||
exit(0);
|
||||
#endif
|
||||
return;
|
||||
|
|
|
@ -583,6 +583,11 @@ SrsGopCache::~SrsGopCache()
|
|||
clear();
|
||||
}
|
||||
|
||||
void SrsGopCache::dispose()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void SrsGopCache::set(bool enabled)
|
||||
{
|
||||
enable_gop_cache = enabled;
|
||||
|
@ -955,6 +960,14 @@ void SrsSource::dispose()
|
|||
#ifdef SRS_AUTO_HLS
|
||||
hls->dispose();
|
||||
#endif
|
||||
|
||||
// cleaup the cached packets.
|
||||
srs_freep(cache_metadata);
|
||||
srs_freep(cache_sh_video);
|
||||
srs_freep(cache_sh_audio);
|
||||
|
||||
// cleanup the gop cache.
|
||||
gop_cache->dispose();
|
||||
}
|
||||
|
||||
int SrsSource::cycle()
|
||||
|
|
|
@ -309,6 +309,10 @@ public:
|
|||
SrsGopCache();
|
||||
virtual ~SrsGopCache();
|
||||
public:
|
||||
/**
|
||||
* cleanup when system quit.
|
||||
*/
|
||||
virtual void dispose();
|
||||
/**
|
||||
* to enable or disable the gop cache.
|
||||
*/
|
||||
|
|
85
trunk/src/core/srs_core_mem_watch.cpp
Normal file
85
trunk/src/core/srs_core_mem_watch.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 SRS(simple-rtmp-server)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <srs_core_mem_watch.hpp>
|
||||
|
||||
#ifdef SRS_MEM_WATCH
|
||||
|
||||
#include <map>
|
||||
#include <stdio.h>
|
||||
using namespace std;
|
||||
|
||||
struct SrsMemoryObject
|
||||
{
|
||||
void* ptr;
|
||||
std::string category;
|
||||
int size;
|
||||
};
|
||||
|
||||
std::map<void*, SrsMemoryObject*> _srs_ptrs;
|
||||
|
||||
void srs_memory_watch(void* ptr, string category, int size)
|
||||
{
|
||||
SrsMemoryObject* obj = NULL;
|
||||
|
||||
std::map<void*, SrsMemoryObject*>::iterator it;
|
||||
if ((it = _srs_ptrs.find(ptr)) != _srs_ptrs.end()) {
|
||||
obj = it->second;
|
||||
} else {
|
||||
obj = new SrsMemoryObject();
|
||||
_srs_ptrs[ptr] = obj;
|
||||
}
|
||||
|
||||
obj->ptr = ptr;
|
||||
obj->category = category;
|
||||
obj->size = size;
|
||||
}
|
||||
|
||||
void srs_memory_unwatch(void* ptr)
|
||||
{
|
||||
std::map<void*, SrsMemoryObject*>::iterator it;
|
||||
if ((it = _srs_ptrs.find(ptr)) != _srs_ptrs.end()) {
|
||||
SrsMemoryObject* obj = it->second;
|
||||
srs_freep(obj);
|
||||
|
||||
_srs_ptrs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void srs_memory_report()
|
||||
{
|
||||
printf("srs memory leak report:\n");
|
||||
|
||||
int total = 0;
|
||||
std::map<void*, SrsMemoryObject*>::iterator it;
|
||||
for (it = _srs_ptrs.begin(); it != _srs_ptrs.end(); ++it) {
|
||||
SrsMemoryObject* obj = it->second;
|
||||
printf(" %s: %#"PRIx64", %dB\n", obj->category.c_str(), (int64_t)obj->ptr, obj->size);
|
||||
total += obj->size;
|
||||
}
|
||||
|
||||
printf("%d objects leak %dKB.\n", (int)_srs_ptrs.size(), total / 1024);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
49
trunk/src/core/srs_core_mem_watch.hpp
Normal file
49
trunk/src/core/srs_core_mem_watch.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 SRS(simple-rtmp-server)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRS_CORE_MEM_WATCH_HPP
|
||||
#define SRS_CORE_MEM_WATCH_HPP
|
||||
|
||||
/*
|
||||
#include <srs_core_mem_watch.hpp>
|
||||
*/
|
||||
|
||||
#include <srs_core.hpp>
|
||||
|
||||
#ifdef SRS_MEM_WATCH
|
||||
|
||||
#include <string>
|
||||
|
||||
// watch the specified memory.
|
||||
extern void srs_memory_watch(void* ptr, std::string category, int size);
|
||||
|
||||
// unwatch the specified memory.
|
||||
extern void srs_memory_unwatch(void* ptr);
|
||||
|
||||
// report the memory watch.
|
||||
extern void srs_memory_report();
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -188,5 +188,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#undef SRS_PERF_FAST_FLV_ENCODER
|
||||
#define SRS_PERF_FAST_FLV_ENCODER
|
||||
|
||||
/**
|
||||
* whether enable the special memory watcher.
|
||||
* which used for memory leak debug and hurts performance.
|
||||
*/
|
||||
#define SRS_MEM_WATCH
|
||||
#undef SRS_MEM_WATCH
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ using namespace std;
|
|||
#include <srs_kernel_file.hpp>
|
||||
#include <srs_kernel_codec.hpp>
|
||||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_core_mem_watch.hpp>
|
||||
|
||||
SrsMessageHeader::SrsMessageHeader()
|
||||
{
|
||||
|
@ -159,6 +160,9 @@ SrsCommonMessage::SrsCommonMessage()
|
|||
|
||||
SrsCommonMessage::~SrsCommonMessage()
|
||||
{
|
||||
#ifdef SRS_MEM_WATCH
|
||||
srs_memory_unwatch(payload);
|
||||
#endif
|
||||
srs_freep(payload);
|
||||
}
|
||||
|
||||
|
@ -171,6 +175,9 @@ SrsSharedPtrMessage::SrsSharedPtrPayload::SrsSharedPtrPayload()
|
|||
|
||||
SrsSharedPtrMessage::SrsSharedPtrPayload::~SrsSharedPtrPayload()
|
||||
{
|
||||
#ifdef SRS_MEM_WATCH
|
||||
srs_memory_unwatch(payload);
|
||||
#endif
|
||||
srs_freep(payload);
|
||||
}
|
||||
|
||||
|
|
|
@ -212,6 +212,11 @@ void check_macro_features()
|
|||
srs_warn("SRS %s is not stable, please use stable branch %s instead", RTMP_SIG_SRS_VERSION, VERSION_STABLE_BRANCH);
|
||||
#endif
|
||||
|
||||
#ifdef SRS_MEM_WATCH
|
||||
#warning "srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script."
|
||||
srs_warn("srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script.");
|
||||
#endif
|
||||
|
||||
#if defined(SRS_AUTO_STREAM_CASTER)
|
||||
#warning "stream caster is experiment feature."
|
||||
srs_warn("stream caster is experiment feature.");
|
||||
|
|
|
@ -72,7 +72,7 @@ SrsFastBuffer::~SrsFastBuffer()
|
|||
|
||||
int SrsFastBuffer::size()
|
||||
{
|
||||
return end - p;
|
||||
return (int)(end - p);
|
||||
}
|
||||
|
||||
char* SrsFastBuffer::bytes()
|
||||
|
|
|
@ -30,6 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_protocol_buffer.hpp>
|
||||
#include <srs_rtmp_utility.hpp>
|
||||
#include <srs_core_mem_watch.hpp>
|
||||
|
||||
// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213
|
||||
#ifndef _WIN32
|
||||
|
@ -364,6 +365,7 @@ int SrsProtocol::recv_message(SrsCommonMessage** pmsg)
|
|||
srs_verbose("entire msg received");
|
||||
|
||||
if (!msg) {
|
||||
srs_info("got empty message without error.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -467,7 +469,7 @@ int SrsProtocol::do_send_messages(SrsSharedPtrMessage** msgs, int nb_msgs)
|
|||
iovs[0].iov_len = nbh;
|
||||
|
||||
// payload iov
|
||||
int payload_size = srs_min(out_chunk_size, pend - p);
|
||||
int payload_size = srs_min(out_chunk_size, (int)(pend - p));
|
||||
iovs[1].iov_base = p;
|
||||
iovs[1].iov_len = payload_size;
|
||||
|
||||
|
@ -1411,6 +1413,9 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, SrsCommonMessage**
|
|||
if (!chunk->msg->payload) {
|
||||
chunk->msg->payload = new char[chunk->header.payload_length];
|
||||
srs_verbose("create payload for RTMP message. size=%d", chunk->header.payload_length);
|
||||
#ifdef SRS_MEM_WATCH
|
||||
srs_memory_watch(chunk->msg->payload, "msg.payload", chunk->header.payload_length);
|
||||
#endif
|
||||
}
|
||||
|
||||
// read payload to buffer
|
||||
|
|
|
@ -289,7 +289,7 @@ std::string _full_conf = ""
|
|||
" bf 3; \n "
|
||||
" refs 10; \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 70; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -320,7 +320,7 @@ std::string _full_conf = ""
|
|||
" vpreset superfast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -349,7 +349,7 @@ std::string _full_conf = ""
|
|||
" vpreset superfast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -379,7 +379,7 @@ std::string _full_conf = ""
|
|||
" vpreset superfast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -396,7 +396,7 @@ std::string _full_conf = ""
|
|||
" engine acodec { \n "
|
||||
" enabled on; \n "
|
||||
" vcodec copy; \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -413,7 +413,7 @@ std::string _full_conf = ""
|
|||
" engine vn { \n "
|
||||
" enabled on; \n "
|
||||
" vcodec vn; \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -461,7 +461,7 @@ std::string _full_conf = ""
|
|||
" bf 3; \n "
|
||||
" refs 10; \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 70; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -483,7 +483,7 @@ std::string _full_conf = ""
|
|||
" vpreset medium; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 70; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -503,7 +503,7 @@ std::string _full_conf = ""
|
|||
" vpreset fast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 60; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -523,7 +523,7 @@ std::string _full_conf = ""
|
|||
" vpreset superfast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -534,7 +534,7 @@ std::string _full_conf = ""
|
|||
" engine vcopy { \n "
|
||||
" enabled on; \n "
|
||||
" vcodec copy; \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -581,7 +581,7 @@ std::string _full_conf = ""
|
|||
" vpreset superfast; \n "
|
||||
" vparams { \n "
|
||||
" } \n "
|
||||
" acodec libaacplus; \n "
|
||||
" acodec libfdk_aac; \n "
|
||||
" abitrate 45; \n "
|
||||
" asample_rate 44100; \n "
|
||||
" achannels 2; \n "
|
||||
|
@ -2586,7 +2586,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_mirror)
|
|||
EXPECT_STREQ("baseline", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("superfast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -2676,7 +2676,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_crop)
|
|||
EXPECT_STREQ("baseline", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("superfast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -2766,7 +2766,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_logo)
|
|||
EXPECT_STREQ("baseline", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("superfast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -2849,7 +2849,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_audio)
|
|||
EXPECT_STREQ("flv", conf.get_engine_iformat(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vfilter(engine).size() == 0);
|
||||
EXPECT_STREQ("copy", conf.get_engine_vcodec(engine).c_str());
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -2933,7 +2933,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_vn)
|
|||
EXPECT_STREQ("flv", conf.get_engine_iformat(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vfilter(engine).size() == 0);
|
||||
EXPECT_STREQ("vn", conf.get_engine_vcodec(engine).c_str());
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3105,7 +3105,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all)
|
|||
EXPECT_STREQ("main", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("medium", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() > 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(70, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3130,7 +3130,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all)
|
|||
EXPECT_STREQ("main", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("medium", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(70, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3155,7 +3155,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all)
|
|||
EXPECT_STREQ("main", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("fast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(60, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3180,7 +3180,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all)
|
|||
EXPECT_STREQ("baseline", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("superfast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3197,7 +3197,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all)
|
|||
EXPECT_STREQ("flv", conf.get_engine_iformat(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vfilter(engine).size() == 0);
|
||||
EXPECT_STREQ("copy", conf.get_engine_vcodec(engine).c_str());
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -3322,7 +3322,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_ffempty)
|
|||
EXPECT_STREQ("baseline", conf.get_engine_vprofile(engine).c_str());
|
||||
EXPECT_STREQ("superfast", conf.get_engine_vpreset(engine).c_str());
|
||||
EXPECT_TRUE((int)conf.get_engine_vparams(engine).size() == 0);
|
||||
EXPECT_STREQ("libaacplus", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_STREQ("libfdk_aac", conf.get_engine_acodec(engine).c_str());
|
||||
EXPECT_EQ(45, conf.get_engine_abitrate(engine));
|
||||
EXPECT_EQ(44100, conf.get_engine_asample_rate(engine));
|
||||
EXPECT_EQ(2, conf.get_engine_achannels(engine));
|
||||
|
@ -4837,12 +4837,12 @@ VOID TEST(ConfigMainTest, CheckConf_transcode)
|
|||
|
||||
if (true) {
|
||||
MockSrsConfig conf;
|
||||
EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"vhost v{transcode{engine {acodec libaacplus;}}}"));
|
||||
EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"vhost v{transcode{engine {acodec libfdk_aac;}}}"));
|
||||
}
|
||||
|
||||
if (true) {
|
||||
MockSrsConfig conf;
|
||||
EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"vhost v{transcode{engine {acodecs libaacplus;}}}"));
|
||||
EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"vhost v{transcode{engine {acodecs libfdk_aac;}}}"));
|
||||
}
|
||||
|
||||
if (true) {
|
||||
|
|
Loading…
Reference in a new issue