ossrs / srs

SRS is a simple, high-efficiency, real-time video server supporting RTMP, WebRTC, HLS, HTTP-FLV, SRT, MPEG-DASH, and GB28181.

Home Page:https://ossrs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

When multiple push/pull stream links of the same stream arrive at the same time, under the condition of enabling the DVR or HLS module, multiple SrsSource may be created for the same stream, leading to memory leaks!

dean-river opened this issue · comments

The code is as follows:
int SrsSource::fetch_or_create(SrsRequest* r, ISrsSourceHandler* h, SrsSource** pps)
{
int ret = ERROR_SUCCESS;

SrsSource* source = NULL;
if ((source = fetch(r)) != NULL) {
    *pps = source;
    return ret;
}

string stream_url = r->get_stream_url();
string vhost = r->vhost;

// should always not exists for create a source.
srs_assert (pool.find(stream_url) == pool.end());

**source = new SrsSource();
if ((ret = source->initialize(r, h)) != ERROR_SUCCESS) {
    srs_freep(source);
    return ret;
}
    
pool[stream_url] = source;**
srs_info("create new source for url=%s, vhost=%s", stream_url.c_str(), vhost.c_str());

*pps = source;

return ret;

}
The following description corresponds to the premise of enabling the DVR and HLS modules.

Above, when the SrsSource object is not found, a new one is created, but it is not immediately put into the pool. Instead, it needs to be initialized. In the initialize function, the DVR and HLS modules are initialized. In the initialize functions of these two modules, the start function of SrsAsyncCallWorker is called, which starts a new coroutine. This will cause a switch to the cycle function of SrsAsyncCallWorker, but it is not necessarily switched back to the coroutine that initialized SrsSource. This means that if a new connection arrives and enters this function, it may create SrsSource again.

In conclusion, the source->initialize function cannot guarantee coroutine-level atomicity, which can lead to this situation.

I have thought of three solutions:

  1. Restrict the initialize function from switching coroutines. Move the start function of SrsAsyncCallWorker to another place, such as the execute function. However, this cannot guarantee that the execute operation does not switch coroutines. Moreover, initialize may perform a lot of work, so this restriction seems not ideal.
  2. Add a mutex lock to the code segment that creates SrsSource and puts it into the pool. However, this will reduce the performance of simultaneously accessing connections.
  3. After initialization is completed and before putting it into the pool, check again if it already exists in the pool. If it does, it means that another coroutine has completed initialization first, so delete the one created by itself and use the one that was put into the pool first. Currently, I am using this method. However, the code looks a bit awkward.

I hope the experts have more elegant solutions to solve this problem!!!

TRANS_BY_GPT3

The competition condition of Source is a problem. This problem was previously overlooked, but essentially it is a problem of thread or coroutine synchronization.

TRANS_BY_GPT3

If there are asynchronous operations during source.initialize, causing ST coroutine switching, it is possible to have multiple Sources for one URL, for example:

(gdb) p stream_url
$2 = "/live/livestream"
(gdb) p source
$3 = (SrsSource *) 0xa66de0

(gdb) p stream_url
$4 = "/live/livestream"
(gdb) p source
$5 = (SrsSource *) 0xa713b0

SrsSourceManager::fetch_or_create needs to be protected by a mutex to prevent ST coroutine switching.

TRANS_BY_GPT3