I have SSL connection in main thread and have memory leak.
I close connection in this way (_ssl — SSL* object):
SSL_shutdown(_ssl);
SSL_free (_ssl);
But memory isn't freed correctly — after every new client some memory (~20kb) doesn't freed. I made an experiment: make many connections(~7000) and close them after establishing. Graph shows that memory was really growing up during all time. It's very important for me that memory will be unchangeable from connection to connection. Any suggestions?
Graph:
UPD:
All functions that I use:
It's executed once when server is start (I think it can't reason for increasing memory from connection to connection):
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
SSL_METHOD* meth = TLSv1_server_method();
_ctx = SSL_CTX_new (meth);
SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify_depth(_ctx, 1);
if (!_ctx) {
ERR_print_errors_fp(stderr);
exit(2);
}
if (SSL_CTX_load_verify_locations(_ctx, "keys/c1.crt", NULL) <= 0)
{
ERR_print_errors_fp(stderr);
exit(3);
}
SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify_depth(_ctx,1);
if (SSL_CTX_use_certificate_file(_ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(3);
}
if (SSL_CTX_use_PrivateKey_file(_ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(4);
}
if (!SSL_CTX_check_private_key(_ctx)) {
fprintf(stderr, "Private key does not match the certificate public key\n");
exit(5);
}
It's executed for every connection in the start:
ERR_remove_state(0);
_ssl = SSL_new (ctx);
if (_ssl == NULL)
{
_isValid = false;
return;
}
SSL_set_fd (_ssl, _sd->get_num());
int err = SSL_accept (_ssl);
if (err == -1)
{
ERR_print_errors_fp(stderr);
_isValid = false;
return;
}
You have to release all resources which you used in this connection. OpenSSL has a reference counter on these resources and some functions returns you something with an increased counter and some not, so you have to really look at the (often insufficient) documentation. OpenSSL which not keep track which resources you've allocated within this connection and give them automatically back once you've called SSL_free, you have to take care of this yourself.
There is not enough information of what you are really doing in your code to see where if there really is a leak and where it comes from. But as an example: if you called SSL_get_peer_certificate to get the certificate you have to explicitly free the memory because (from the documentation):
The reference count of the X509 object is incremented by one, so that it will not be destroyed when the session containing the peer certificate is freed. The X509 object must be explicitly freed using X509_free().
Contrary to that the similar function SSL_get_peer_chain will not increase the reference counter so you should not attempt to free the resources yourself:
The reference count of the STACK_OF(X509) object is not incremented. If the corresponding session is freed, the pointer must not be used any longer.
So you have to actually look at each function you use and check if you need to free allocated resources explicitly or not.
Related
Can anyone clarify why do I get "Invalid pointer operation" when I attempt to delete TStreamAdapter? Or... how to properly free the memory from TStreamAdapter? It works, if I remove the delete but that causes a memory leak. Even if I use boost::scoped_ptr it also fails with the same error.
Note: I also tried initializing TStreamAdapter with soOwned value, same error.
The code:
HRESULT LoadFromStr(TWebBrowser* WB, const UnicodeString& HTML)
{
if (!WB->Document)
{
WB->Navigate("about:blank");
while (!WB->Document) { Application->ProcessMessages(); }
}
DelphiInterface<IHTMLDocument2> diDoc = WB->Document;
if (diDoc)
{
boost::scoped_ptr<TMemoryStream> ms(new TMemoryStream);
{
boost::scoped_ptr<TStringList> sl(new TStringList);
sl->Text = HTML;
sl->SaveToStream(ms.get(), TEncoding::Unicode);
ms->Position = 0;
}
DelphiInterface<IPersistStreamInit> diPSI;
if (SUCCEEDED(diDoc->QueryInterface(IID_IPersistStreamInit, (void**)&diPSI)) && diPSI)
{
TStreamAdapter* sa = new TStreamAdapter(ms.get(), soReference);
diPSI->Load(*sa);
delete sa; // <-- invalid pointer operation here???
// UPDATED (solution) - instead of the above!!!
// DelphiInterface<IStream> sa(*(new TStreamAdapter(ms.get(), soReference)));
// diPSI->Load(sa);
// DelphiInterface is automatically freed on function end
return S_OK;
}
}
return E_FAIL;
}
Update: I found the solution here - http://www.cyberforum.ru/cpp-builder/thread743255.html
The solution is to use
_di_IStream sa(*(new TStreamAdapter(ms.get(), soReference)));
or...
DelphiInterface<IStream> sa(*(new TStreamAdapter(ms.get(), soReference)));
As it will automatically free the IStream once it is out of scope. At least it should - is there a possible memory leak here? (CodeGuard did not detect any memory leaks).
TStreamAdapter is a TInterfacedObject descendant, which implements reference counting semantics. You are not supposed to delete it at all, you need to let reference counting free the object when it is no longer being referenced by anyone.
Using _di_IStream (which is just an alias for DelphiInterface<IStream>) is the correct way to automate that with a smart pointer. TComInterface<IStream> and CComPtr<IStream> would also work, too.
On Mac OSX with Objective C and Distributed Objects (IPC API), how can a server determine if the client has closed down their application and thus the NSConnection pointer has been broken? I mean, I might be in a while loop, reading a lot of data to send back from the server to the client, and I can test for stuff if I just knew what the trick was to determine if the client has suddenly disconnected.
Add this to your server's vended object class and then check it when doing something in any kind of loop for long periods of time. Replace "servicetest" phrase in the code below with the name of your actual process. You can run your client app and then check ps -ef at command line to see what your process is, if uncertain. It'll be the one with DerivedData if running temporarily in XCode.
A little warning, though. This routine is kind of heavy and you shouldn't call it with every loop iteration. Instead, check like every 500 iterations of your while loop you might be in, for instance. You could also have a background timer that runs and checks. Or you could use rand to determine 1 in 5 odds and run it then.
Note that this routine only runs properly with Distributed Objects on the same system, as in application to application IPC, rather than Distributed Objects over a LAN.
So, when I detected that a client suddenly disconnected, I do a break on my while loop (where I was reading slow process data) and then did a pclose() on that process because there was no sense running it anymore.
/* requires these includes
#include <libproc.h>
#include <sys/sysctl.h>
#include <string>
*/
- (bool)_clientSuddenlyDisconnected;
{
int numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
pid_t pids[1024];
bzero(pids, 1024);
proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
for (int i = 0; i < numberOfProcesses; ++i) {
if (pids[i] == 0) { continue; }
char pathBuffer[PROC_PIDPATHINFO_MAXSIZE];
bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE);
proc_pidpath(pids[i], pathBuffer, sizeof(pathBuffer));
if (strlen(pathBuffer) > 0) {
std::string sTest(pathBuffer);
sTest = "/" + sTest + "$$$";
if (sTest.find("/servicetest$$$") != std::string::npos) {
return false;
}
}
}
return true;
}
I am receiving messages on UDP in multiple threads. After each reception I raise MessageReceived.OnNext(message).
Because I am using multiple threads the messages raised unordered which is a problem.
How can I order the raise of the messages by the message counter?
(lets say there is a message.counter property)
Must take in mind a message can get lost in the communication (lets say if we have a counter hole after X messages that the hole is not filled I raise the next message)
Messages must be raised ASAP (if the next counter received)
In stating the requirement for detecting lost messages, you haven't considered the possibility of the last message not arriving; I've added a timeoutDuration which flushes the buffered messages if nothing arrives in the given time - you may want to consider this an error instead, see the comments for how to do this.
I will solve this by defining an extension method with the following signature:
public static IObservable<TSource> Sort<TSource>(
this IObservable<TSource> source,
Func<TSource, int> keySelector,
TimeSpan timeoutDuration = new TimeSpan(),
int gapTolerance = 0)
source is the stream of unsorted messages
keySelector is a function that extracts an int key from a message. I assume the first key sought is 0; amend if necessary.
timeoutDuration is discussed above, if omitted, there is no timeout
tolerance is the maximum number of messages held back while waiting for an out of order message. Pass 0 to hold any number of messages
scheduler is the scheduler to use for the timeout and is supplied for test purposes, a default is used if not given.
Walkthrough
I'll present a line-by-line walkthrough here. The full implementation is repeated below.
Assign Default Scheduler
First of all we must assign a default scheduler if none was supplied:
scheduler = scheduler ?? Scheduler.Default;
Arrange Timeout
Now if a time out was requested, we will replace the source with a copy that will simply terminate and send OnCompleted if a message doesn't arrive in timeoutDuration.
if(timeoutDuration != TimeSpan.Zero)
source = source.Timeout(
timeoutDuration,
Observable.Empty<TSource>(),
scheduler);
If you wish to send a TimeoutException instead, just delete the second parameter to Timeout - the empty stream, to select an overload that does this. Note we can safely share this with all subscribers, so it is positioned outside the call to Observable.Create.
Create Subscribe handler
We use Observable.Create to build our stream. The lambda function that is the argument to Create is invoked whenever a subscription occurs and we are passed the calling observer (o). Create returns our IObservable<T> so we return it here.
return Observable.Create<TSource>(o => { ...
Initialize some variables
We will track the next expected key value in nextKey, and create a SortedDictionary to hold the out of order messages until they can be sent.
int nextKey = 0;
var buffer = new SortedDictionary<int, TSource>();
Subscribe to the source, and handle messages
Now we can subscribe to the message stream (possibly with the timeout applied). First we introduce the OnNext handler. The next message is assigned to x:
return source.Subscribe(x => { ...
We invoke the keySelector function to extract the key from the message:
var key = keySelector(x);
If the message has an old key (because it exceeded our tolerance for out of order messages) we are just going to drop it and be done with this message (you may want to act differently):
// drop stale keys
if(key < nextKey) return;
Otherwise, we might have the expected key, in which case we can increment nextKey send the message:
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
Or, we might have an out of order future message, in which case we must add it to our buffer. If we do this, we must also ensure our buffer hasn't exceeded our tolerance for storing out of order messages - in this case, we will also bump nextKey to the first key in the buffer which because it is a SortedDictionary is conveniently the next lowest key:
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
Now regardless of the outcome above, we need to empty the buffer of any keys that are now ready to go. We use a helper method for this. Note that it adjusts nextKey so we must be careful to pass it by reference. We simply loop over the buffer reading, removing and sending messages as long as the keys follow on from each other, incrementing nextKey each time:
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
Dealing with errors
Next we supply an OnError handler - this will just pass through any error, including the Timeout exception if you chose to go that way.
Flushing the buffer
Finally, we must handle OnCompleted. Here I have opted to empty the buffer - this would be necessary if an out of order message held up messages and never arrived. This is why we need a timeout:
() => {
// empty buffer on completion
foreach(var item in buffer)
o.OnNext(item.Value);
o.OnCompleted();
});
Full Implementation
Here is the full implementation.
public static IObservable<TSource> Sort<TSource>(
this IObservable<TSource> source,
Func<TSource, int> keySelector,
int gapTolerance = 0,
TimeSpan timeoutDuration = new TimeSpan(),
IScheduler scheduler = null)
{
scheduler = scheduler ?? Scheduler.Default;
if(timeoutDuration != TimeSpan.Zero)
source = source.Timeout(
timeoutDuration,
Observable.Empty<TSource>(),
scheduler);
return Observable.Create<TSource>(o => {
int nextKey = 0;
var buffer = new SortedDictionary<int, TSource>();
return source.Subscribe(x => {
var key = keySelector(x);
// drop stale keys
if(key < nextKey) return;
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
SendNextConsecutiveKeys(ref nextKey, o, buffer);
},
o.OnError,
() => {
// empty buffer on completion
foreach(var item in buffer)
o.OnNext(item.Value);
o.OnCompleted();
});
});
}
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
Test Harness
If you include nuget rx-testing in a console app, the following will run given you a test harness to play with:
public static void Main()
{
var tests = new Tests();
tests.Test();
}
public class Tests : ReactiveTest
{
public void Test()
{
var scheduler = new TestScheduler();
var xs = scheduler.CreateColdObservable(
OnNext(100, 0),
OnNext(200, 2),
OnNext(300, 1),
OnNext(400, 4),
OnNext(500, 5),
OnNext(600, 3),
OnNext(700, 7),
OnNext(800, 8),
OnNext(900, 9),
OnNext(1000, 6),
OnNext(1100, 12),
OnCompleted(1200, 0));
//var results = scheduler.CreateObserver<int>();
xs.Sort(
keySelector: x => x,
gapTolerance: 2,
timeoutDuration: TimeSpan.FromTicks(200),
scheduler: scheduler).Subscribe(Console.WriteLine);
scheduler.Start();
}
}
Closing comments
There's all sorts of interesting alternative approaches here. I went for this largely imperative approach because I think it's easiest to follow - but there's probably some fancy grouping shenanigans you can employ to do this to. One thing I know to be consistently true about Rx - there's always many ways to skin a cat!
I'm also not entirely comfortable with the timeout idea here - in a production system, I would want to implement some means of checking connectivity, such as a heartbeat or similar. I didn't get into this because obviously it will be application specific. Also, heartbeats have been discussed on these boards and elsewhere before (such as on my blog for example).
Strongly consider using TCP instead if you want reliable ordering - that's what it's for; otherwise, you'll be forced to play a guessing game with UDP and sometimes you'll be wrong.
For example, imagine that you receive the following datagrams in this order: [A, B, D]
When you receive D, how long should you wait for C to arrive before pushing D?
Whatever duration you choose you may be wrong:
What if C was lost during transmission and so it will never arrive?
What if the duration you chose is too short and you end up pushing D but then receive C?
Perhaps you could choose a duration that heuristically works best, but why not just use TCP instead?
Side Note:
MessageReceived.OnNext implies that you're using a Subject<T>, which is probably unnecessary. Consider converting the async UdpClient methods into observables directly instead, or convert them by writing an async iterator via Observable.Create<T>(async (observer, cancel) => { ... }).
I am finding the doc for CFStreamCreatePairWithSocketToCFHost confusing:
Specifically, its not clear to me how the function can set the readStream pointer to null on error.
as far as I understand, the pointer is passed by value - so the function can only change the objected pointed to by the pointer.
Right now I can't figure out how to detect connection errors.
Relevant doc snippet:
Creates readable and writable streams connected to a given CFHost object.
void CFStreamCreatePairWithSocketToCFHost (
CFAllocatorRef alloc,
CFHostRef host,
SInt32 port,
CFReadStreamRef *readStream,
CFWriteStreamRef *writeStream
);
readStream
Upon return, contains a CFReadStream object connected to the host host on port port, or NULL if there is a failure during creation. If you pass NULL, the function will not create a readable stream. Ownership follows the Create Rule.
This is my connecting code, it goes all the way to NSLog(#"Connected") even when the server is down.
NSLog(#"Attempting to (re)connect to %#:%d", m_host, m_port);
while(TRUE)
{
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)m_host);
if (!host)
{
NSLog(#"Error resolving host %#", m_host);
[NSThread sleepForTimeInterval:5.0];
continue;
}
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host , m_port, &m_in, &m_out);
CFRelease(host);
if (!m_in)
{
NSLog(#"Error");
}
CFStreamClientContext context = {0, self,nil,nil,nil};
if (CFReadStreamSetClient(m_in, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkReadEvent, &context))
{
CFReadStreamScheduleWithRunLoop(m_in, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
if (CFWriteStreamSetClient(m_out, kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkWriteEvent, &context))
{
CFWriteStreamScheduleWithRunLoop(m_out, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
BOOL success = CFReadStreamOpen(m_in);
CFErrorRef error = CFReadStreamCopyError(m_in);
if (!success || (error && CFErrorGetCode(error) != 0))
{
NSLog(#"Connect error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
[NSThread sleepForTimeInterval:5.0];
}
else
{
NSLog(#"Connected");
break;
}
}
From the "CFNetwork Programming Guide":
Opening a stream can be a lengthy process, so the CFReadStreamOpen and CFWriteStreamOpen functions avoid blocking by returning TRUE to
indicate that the process of opening the stream has begun. To check
the status of the open, call the functions CFReadStreamGetStatus and
CFWriteStreamGetStatus, which returnkCFStreamStatusOpening if the open
is still in progress, kCFStreamStatusOpen if the open is complete,
orkCFStreamStatusErrorOccurred if the open has completed but failed.
In most cases, it doesn’t matter whether the open is complete because
the CFStream functions that read and write will block until the stream
is open.
Also check out the kCFStreamEventOpenCompleted,
(http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFStreamConstants/Reference/reference.html)
: a stream event that reports the successful completion of the opening
process. So to conclude, after calling CFReadStreamOpen (or Write),
which will probably succeed, register to listen to the "OpenCompleted"
event to identify a "real" success.
Surely after you call CFStreamCreatePairWithSocketToCFHost() just test readstream to see if it's NULL?
As you're passing in the memory location of the readstream pointer, the function can easily set that to whatever value it chooses (either a reference to a created object, or alternatively NULL).
Edit
I've tried your code, and I agree, it's very confusing. It appears that the CFReadStreamRef is readily created and opened, even for a nonsense host (I literally used "nonsense"). I don't believe this function will return NULL pointers for an unreachable host.
I suppose this makes sense, in as far as until one tries to open the stream, whether it will work or not is unknown.
So, the readStream param is a pointer to the CFReadStreamRef and, as such, can definitely be set to NULL by the function. &foo means "address of foo" and if you have the address you can set the value.
My reading of the documentation for CFStreamCreatePairWithSocketToCFHost is that they will be set to NULL on failure, but that failure is not about connection failure, but other kinds of failure (memory, etc). So not likely you'll get an error there.
Looks to me like the issue is that CFReadStreamOpen can return immediately with true when it can open the stream in the background and so this code is not really opening the stream or testing that it's been opened, merely queuing it for opening). From the documentation for CFReadStreamOpen:
" If the stream can open in the background without blocking, this function always returns true."
So I think you will need to follow the rest of the instructions for CFReadStreamOpen and schedule the stream on a run loop, or perhaps poll (though obviously polling in a tight loop isn't likely to work).
In the documentation for CFReadStreamOpen we see:
Opening a stream causes it to reserve all the system resources it requires. If the stream can open in the background without blocking, this function always returns true.
I suspect that the stream is opening in the background, and thus you are saying "Connected" before it actually opens. You've already scheduled the stream with a runloop, so if you let the run loop run, you'll probably get a callback with the event type set to kCFStreamEventErrorOccurred, and from there you can process the error appropriately.
I'm trying to monitor new audio sessions via Windows 7's IAudioSessionManager2 COM interface (coupled with IAudioSessionNotification). Currently, IAudioSessionNotification::OnSessionCreated() is never called and I've run out of ideas as to why.
Code registering custom IAudioSessionNotification:
#define SAFE_RELEASE(comObj) \
if(comObj != NULL) \
{ (comObj)->Release(); comObj = NULL; }
BOOL success = false;
HRESULT res;
IClassFactory* pFactory;
IMMDevice* pDevice;
IMMDeviceEnumerator* pEnumerator;
SESSION_LISTENER = NULL;
SESSION = NULL;
res = CoInitialize(NULL);
if(res != S_OK && res != S_FALSE)
return false;
res = CoGetClassObject(CLSID_CustomAudioFactory, CLSCTX_ALL, NULL, __uuidof(IClassFactory), (void**)&pFactory);
if(res != S_OK) goto Exit;
res = pFactory->CreateInstance(NULL, CLSID_CustomAudioNotifications, (void**)&SESSION_LISTENER);
if(res != S_OK) goto Exit;
res = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if(res != S_OK) goto Exit;
res = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
if(res != S_OK) goto Exit;
res = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&SESSION);
if(res != S_OK) goto Exit;
res = SESSION->RegisterSessionNotification(SESSION_LISTENER);
if(res != S_OK) goto Exit;
success = true;
Exit:
SAFE_RELEASE(pFactory);
SAFE_RELEASE(pEnumerator);
SAFE_RELEASE(pDevice);
if(!success)
{
SAFE_RELEASE(SESSION_LISTENER);
SAFE_RELEASE(SESSION);
}
CustomAudioNotifications declaration:
class CustomAudioNotifications : public IAudioSessionNotification
{
public:
//Constructors
CustomAudioNotifications() { InterlockedIncrement(&g_notifyCount); m_listener = NULL; }
~CustomAudioNotifications() { InterlockedDecrement(&g_notifyCount); SAFE_RELEASE(m_listener); }
//IUnknown interface
HRESULT __stdcall QueryInterface(
REFIID riid ,
void **ppObj);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
//Notification
HRESULT __stdcall OnSessionCreated(IAudioSessionControl *NewSession);
private:
LONG m_nRefCount;
};
OnSessionCreated just posts a message to a window whenever a session is created for the time being; which never happens. Just in case my assumptions are totally off base, I'm expecting a notification whenever an application that has yet to play audio starts to do so; so launching VLC with a video file should immediately result in a notice, while visiting Pandora via a web browser would also trigger such a notice.
Debugging shows all returned values are S_OK.
My COM experience is pretty limitted, so pointing out general "WTFs?" would also be appreciated.
That's a TON more work than you need to do.
You just need to write a class that derives from IAudioSessionNotifications - you don't need to actually write a whole COM object and register it.
You should also use the eConsole role instead of the eMultimedia role. It doesn't effectively matter (if you have only one audio device) but it's more correct.
The destructor for the CustomAudioNotification class should be private - that way you prevent accidental destruction. So I'd write:
CustomAudioNotification
*customNotification = new CustomAudioNotification();
SESSION->RegisterSessionNotification(customNotification);
I'm also assuming that you've initialized COM before your code snippet.
UPDATED: Kevin sent me his application and there are a couple of other issues with his application that are more fundamental (I'm working to get the documentation for the APIs improve to prevent any confusion in the future)
The first is that his application hasn't retrieved the current list of sessions. This is one of the really subtle things about the session enumeration APIs. In order to prevent a race condition that can occur when a session notification arrives while the application using the session APIs is starting up, the session enumeration API discards new session notifications until the application has first retrieved the list of existing sessions.
The expected usage pattern is:
Application activates a session manager2.
Application registers for session notifications.
Application retrieves the current list of sessions for the endpoint and stores the session control objects into a list (don't forget to addref the session).
When a new session is created, the application takes a reference to the newly created session control object and inserts it into the list if it's not already present. Note that the session control object passed into the notification will be destroyed when the session notification returns - if you call GetSessionEnumerator at this point it will probably NOT hold the newly created session (it might, it all depends on timing).
The application manages the lifetime of the session based on its own criteria - as long as the application has a reference to the session control the session control object will be valid. There is no expiration mechanism for audio session control objects.
In addition, the session APIs require that the MTA be initialized - this is unfortunate but because we create COM objects (which implement IAudioSessionControl) on a worker thread the API requires that the MTA be created before the notification is received.