summaryrefslogtreecommitdiff
path: root/src/vm/profdetach.cpp
blob: 868917942a1695f17916f08b9716058acb2e7a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
//
// ProfDetach.cpp
// 

//
// Implementation of helper classes and structures used for Profiling API Detaching
//
// ======================================================================================

#include "common.h"

#ifdef FEATURE_PROFAPI_ATTACH_DETACH

#include "profdetach.h"
#include "profilinghelper.h"

// Class static member variables
ProfilerDetachInfo ProfilingAPIDetach::s_profilerDetachInfo;
CLREvent           ProfilingAPIDetach::s_eventDetachWorkAvailable;


// ---------------------------------------------------------------------------------------
// ProfilerDetachInfo constructor
//
// Description:
//    Set every member variable to NULL or 0.  They'll get initialized to real values
//    in ProfilingAPIDetach::RequestProfilerDetach.
//    

ProfilerDetachInfo::ProfilerDetachInfo()
{
    // Executed during construction of a global object, therefore we cannot
    // use real contracts, as this requires that utilcode has been initialized.
    STATIC_CONTRACT_NOTHROW;
    STATIC_CONTRACT_MODE_ANY;
    STATIC_CONTRACT_GC_NOTRIGGER;
    STATIC_CONTRACT_CANNOT_TAKE_LOCK;

    Init();
}

void ProfilerDetachInfo::Init()
{
    // Executed during construction of a global object, therefore we cannot
    // use real contracts, as this requires that utilcode has been initialized.
    STATIC_CONTRACT_LEAF;
    
    m_pEEToProf = NULL;
    m_ui64DetachStartTime = 0;
    m_dwExpectedCompletionMilliseconds = 0;
}


// ----------------------------------------------------------------------------
// Implementation of ProfilingAPIAttachDetach statics


// ----------------------------------------------------------------------------
// ProfilingAPIDetach::Initialize
// 
// Description:
//    Initialize static event

// static 
HRESULT ProfilingAPIDetach::Initialize()
{
    CONTRACTL
    {
        NOTHROW;
        MODE_ANY;
        GC_TRIGGERS;
    }
    CONTRACTL_END;

    if (!s_eventDetachWorkAvailable.IsValid())
    {
        HRESULT hr = S_OK;

        EX_TRY
        {
            s_eventDetachWorkAvailable.CreateAutoEvent(FALSE);
        }
        EX_CATCH
        {
            hr = GET_EXCEPTION()->GetHR();
            if (SUCCEEDED(hr))
            {
                // For exceptions that give us useless hr's, just use E_FAIL
                hr = E_FAIL;
            }
        }
        EX_END_CATCH(RethrowTerminalExceptions)

        if (FAILED(hr))
        {
            return hr;
        }
    }

    return S_OK;
}



// ----------------------------------------------------------------------------
// ProfilingAPIDetach::RequestProfilerDetach
// 
// Description:
//    Initialize ProfilerDetachInfo structures with parameters passed from
//    ICorProfilerInfo3::RequestProfilerDetach
//    
// Arguments:
//    * dwExpectedCompletionMilliseconds - A hint to the CLR as to how long it should
//        wait before checking to see if execution has evacuated the profiler and all
//        profiler-instrumented code. If this is 0, the CLR will select a default.
//      
// Notes:
// 
//    Invariants maintained by profiler:
//    * Before calling RequestProfilerDetach, the profiler must turn off all hijacking.
//    * If RequestProfilerDetach is called from a thread created by the CLR (i.e., from
//        within a callback), the profiler must first have exited all threads of its own
//        creation
//    * If RequestProfilerDetach is called from a thread of the profiler's own creation,
//        then
//        * The profiler must first have exited all OTHER threads of its own creation,
//            AND
//        * The profiler must immediately call FreeLibraryAndExitThread() after
//            RequestProfilerDetach returns.
//
//    The above invariants result in the following possiblities:
//        * RequestProfilerDetach() may be called multi-threaded, but only from within
//            profiler callbacks. As such, evacuation counters will have been incremented
//            before entry into RequestProfilerDetach(), so the DetachThread will be
//            blocked until all such threads have returned from RequestProfilerDetach and
//            the callback from which RequestProfilerDetach was called. OR
//        * RequestProfilerDetach() is called single-threaded, from a thread of the
//            profiler's creation, which promises not to make any more calls into the CLR
//            afterward. In this case, the DetachThread will be blocked until
//            RequestProfilerDetach signals s_eventDetachWorkAvailable at the end.
//            

// static
HRESULT ProfilingAPIDetach::RequestProfilerDetach(DWORD dwExpectedCompletionMilliseconds)
{
    CONTRACTL
    {
        NOTHROW;
        // Crst is used so GC may be triggered
        GC_TRIGGERS;
        MODE_ANY;
        EE_THREAD_NOT_REQUIRED;
        // Crst is used to synchronize the initialization of ProfilingAPIDetach internal structure
        CAN_TAKE_LOCK;
        PRECONDITION(ProfilingAPIUtility::GetStatusCrst() != NULL);
        PRECONDITION(s_eventDetachWorkAvailable.IsValid());
    }
    CONTRACTL_END;

    // Runtime must be fully started, or else CpuStoreBufferControl used below may not
    // be initialized yet.
    if (!g_fEEStarted)
    {
        return CORPROF_E_RUNTIME_UNINITIALIZED;
    }

    if (dwExpectedCompletionMilliseconds == 0)
    {
        // Pick suitable default if the profiler just leaves this at 0.  5 seconds is
        // reasonable.
        dwExpectedCompletionMilliseconds = 5000;
    }

    {
        CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst());

        // return immediately if detach is in progress

        if (s_profilerDetachInfo.m_pEEToProf != NULL)
        {
            return CORPROF_E_PROFILER_DETACHING;
        }

        ProfilerStatus curProfStatus = g_profControlBlock.curProfStatus.Get();

        if ((curProfStatus == kProfStatusInitializingForStartupLoad) ||
            (curProfStatus == kProfStatusInitializingForAttachLoad))
        {
            return CORPROF_E_PROFILER_NOT_YET_INITIALIZED;
        }

        if (curProfStatus != kProfStatusActive)
        {
            // Before we acquired the lock, someone else must have unloaded the profiler
            // for us (e.g., shutdown or the DetachThread in response to a prior
            // RequestProfilerDetach call).
            return CORPROF_E_PROFILER_DETACHING;
        }

        EEToProfInterfaceImpl * pEEToProf = g_profControlBlock.pProfInterface;

        // Since prof status was active after entering the lock, the profiler must not
        // have unloaded out from under us.
        _ASSERTE(pEEToProf != NULL);

        if (!pEEToProf->IsCallback3Supported())
        {
            return CORPROF_E_CALLBACK3_REQUIRED;
        }

        // Did the profiler do anything immutable?  That will prevent us from allowing it to
        // detach.
        HRESULT hr = pEEToProf->EnsureProfilerDetachable();
        if (FAILED(hr))
        {
            return hr;
        }
        s_profilerDetachInfo.m_pEEToProf = pEEToProf;
        s_profilerDetachInfo.m_ui64DetachStartTime = CLRGetTickCount64();
        s_profilerDetachInfo.m_dwExpectedCompletionMilliseconds = dwExpectedCompletionMilliseconds;

        // Ok, time to seal the profiler from receiving or making calls with the CLR.
        // (This will force a FlushStoreBuffers().)
        g_profControlBlock.curProfStatus.Set(kProfStatusDetaching);
    }

    // Sealing done. Wake up the DetachThread so it can loop until the profiler code is
    // fully evacuated off of all stacks.
    if (!s_eventDetachWorkAvailable.Set())
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // FUTURE: Currently, kProfStatusDetaching prevents callbacks from being sent to the
    // profiler AND prevents another profiler from attaching. In the future, when
    // implementing the reattach-with-neutered-profilers feature crew, we may want to add
    // another block here to call ProfilingAPIUtility::SetProfStatus(kProfStatusNone), so callbacks are
    // prevented but a new profiler may attempt to attach.

    EX_TRY
    {
        ProfilingAPIUtility::LogProfInfo(IDS_PROF_DETACH_INITIATED);
    }
    EX_CATCH
    {
        // Oh well, rest of detach succeeded, so we should still return success to the
        // profiler.
    }
    EX_END_CATCH(RethrowTerminalExceptions);
    
    return S_OK;
}

//---------------------------------------------------------------------------------------
//
// This is where the DetachThread spends its life.  This waits until there's a profiler
// to detach, then loops until the profiler code is completely evacuated off all stacks.
// This will then unload the profiler.
//

// static
void ProfilingAPIDetach::ExecuteEvacuationLoop()
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_PREEMPTIVE;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    // Wait until there's a profiler to detach (or until this thread should "wake up"
    // for some other reason, such as exiting due to an unsuccessful startup-load of a
    // profiler).
    DWORD dwRet = s_eventDetachWorkAvailable.Wait(INFINITE, FALSE /* alertable */);
    if (dwRet != WAIT_OBJECT_0)
    {
        // The wait ended due to a failure or a reason other than the event getting
        // signaled (e.g., WAIT_ABANDONED)
        DWORD dwErr;
        if (dwRet == WAIT_FAILED)
        {
            dwErr = GetLastError();
            LOG((
                LF_CORPROF, 
                LL_ERROR, 
                "**PROF: DetachThread wait for s_eventDetachWorkAvailable failed with GetLastError = %d.\n",
                dwErr));
        }
        else
        {
            dwErr = dwRet;      // No extra error info available beyond the return code
            LOG((
                LF_CORPROF, 
                LL_ERROR, 
                "**PROF: DetachThread wait for s_eventDetachWorkAvailable terminated with %d.\n",
                dwErr));
        }

        ProfilingAPIUtility::LogProfError(IDS_PROF_DETACH_THREAD_ERROR, dwErr);
        return;
    }

    // Peek to make sure there's actually a profiler to detach
    {
        CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst());

        if (s_profilerDetachInfo.m_pEEToProf == NULL)
        {
            // Nothing to detach.  This can happen if the DetachThread (i.e., current
            // thread) was created but then the profiler failed to load.
            return;
        }
    }

    do
    {
        // Give profiler a chance to return from its procs
        SleepWhileProfilerEvacuates();
    }
    while (!IsProfilerEvacuated());

    UnloadProfiler();
}

//---------------------------------------------------------------------------------------
//
// This is called in between evacuation counter checks.  This calculates how long to
// sleep, and then sleeps.
//

// static
void ProfilingAPIDetach::SleepWhileProfilerEvacuates()
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_PREEMPTIVE;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    // Don't want to check evacuation any more frequently than every 300ms
    const DWORD kdwDefaultMinSleepMs = 300;

    // The default "steady state" max sleep is how long we'll wait if, after a couple
    // tries the profiler still hasn't evacuated. Default to every 10 minutes
    const DWORD kdwDefaultMaxSleepMs = 600000;

    static DWORD s_dwMinSleepMs = 0;
    static DWORD s_dwMaxSleepMs = 0;

    // First time through, initialize the static min / max sleep times.  Normally, we'll
    // just use the constants above, but the user may customize these (within reason).
    
    // They should either both be uninitialized or both initialized
    _ASSERTE(
        ((s_dwMinSleepMs == 0) && (s_dwMaxSleepMs == 0)) ||
        ((s_dwMinSleepMs != 0) && (s_dwMaxSleepMs != 0)));
    
    if (s_dwMaxSleepMs == 0)
    {
        // No race here, since only the DetachThread runs this code
        
        s_dwMinSleepMs = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_DetachMinSleepMs);
        s_dwMaxSleepMs = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_DetachMaxSleepMs);

        // Here's the "within reason" part:  the user may not customize these values to
        // be more "extreme" than the constants, or to be 0 (which would confuse the
        // issue of whether these statics were intialized yet).
        if ((s_dwMinSleepMs < kdwDefaultMinSleepMs) || (s_dwMinSleepMs > kdwDefaultMaxSleepMs))
        {
            // Sleeping less than 300ms between evac checks could negatively affect the
            // app by having the DetachThread execute too often.  And a min sleep time
            // that's too high could result in a profiler hanging around way too long
            // when it's actually ready to be unloaded.
            s_dwMinSleepMs = kdwDefaultMinSleepMs;
        }
        if ((s_dwMaxSleepMs < kdwDefaultMinSleepMs) || (s_dwMaxSleepMs > kdwDefaultMaxSleepMs))
        {
            // A steady state that's too small would retry the evac checks too often on
            // an ongoing basis.  A steady state that's too high could result in a
            // profiler hanging around way too long when it's actually ready to be
            // unloaded.
            s_dwMaxSleepMs = kdwDefaultMaxSleepMs;
        }
    }

    // Take note of when the detach was requested and how long to sleep for
    ULONGLONG ui64ExpectedCompletionMilliseconds;
    ULONGLONG ui64DetachStartTime;
    {
        CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst());

        _ASSERTE(s_profilerDetachInfo.m_pEEToProf != NULL);
        ui64ExpectedCompletionMilliseconds = s_profilerDetachInfo.m_dwExpectedCompletionMilliseconds;
        ui64DetachStartTime = s_profilerDetachInfo.m_ui64DetachStartTime;
    }

    // ui64SleepMilliseconds is calculated to ensure that CLR checks evacuation status roughly:
    //     * After profiler's ui64ExpectedCompletionMilliseconds hint has elapsed (but not
    //         too soon)
    //     * At least once more after 2*ui64ExpectedCompletionMilliseconds have elapsed
    //         (but not too soon)
    //     * Occasionally thereafter (steady state)

    ULONGLONG ui64ElapsedMilliseconds = CLRGetTickCount64() - ui64DetachStartTime;
    ULONGLONG ui64SleepMilliseconds;
    if (ui64ExpectedCompletionMilliseconds > ui64ElapsedMilliseconds)
    {
        // Haven't hit ui64ExpectedCompletionMilliseconds yet, so sleep for remainder
        ui64SleepMilliseconds = ui64ExpectedCompletionMilliseconds - ui64ElapsedMilliseconds;
    }
    else if ((2*ui64ExpectedCompletionMilliseconds) > ui64ElapsedMilliseconds)
    {
        // We're between ui64ExpectedCompletionMilliseconds &
        // 2*ui64ExpectedCompletionMilliseconds, so sleep until
        // 2*ui64ExpectedCompletionMilliseconds have transpired
        ui64SleepMilliseconds = (2*ui64ExpectedCompletionMilliseconds) - ui64ElapsedMilliseconds;
    }
    else
    {
        // Steady state
        ui64SleepMilliseconds = s_dwMaxSleepMs;
    }

    // ...but keep it in bounds!
    ui64SleepMilliseconds = min(
        max(ui64SleepMilliseconds, s_dwMinSleepMs), 
        s_dwMaxSleepMs);

    // At this point it's safe to cast ui64SleepMilliseconds down to a DWORD since we
    // know it's between s_dwMinSleepMs & s_dwMaxSleepMs
    _ASSERTE(ui64SleepMilliseconds <= 0xFFFFffff);
    ClrSleepEx((DWORD) ui64SleepMilliseconds, FALSE /* alertable */);
}

//---------------------------------------------------------------------------------------
//
// Performs the evacuation checks by grabbing the thread store lock, iterating through
// all EE Threads, and querying each one's evacuation counter.  If they're all 0, the
// profiler is ready to be unloaded.
//
// Return Value:
//    Nonzero iff the profiler is fully evacuated and ready to be unloaded.
//

// static
BOOL ProfilingAPIDetach::IsProfilerEvacuated()
{
    CONTRACTL
    {
        NOTHROW;
        GC_TRIGGERS;
        MODE_ANY;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    _ASSERTE(g_profControlBlock.curProfStatus.Get() == kProfStatusDetaching);

    // Check evacuation counters on all the threads (see
    // code:ProfilingAPIUtility::InitializeProfiling#LoadUnloadCallbackSynchronization
    // for details). Doing this under the thread store lock not only ensures we can
    // iterate through the Thread objects safely, but also forces us to serialize with
    // the GC. The latter is important, as server GC enters the profiler on non-EE
    // Threads, and so no evacuation counters might be incremented during server GC even
    // though control could be entering the profiler.
    {
        ThreadStoreLockHolder TSLockHolder;

        Thread * pThread = ThreadStore::GetAllThreadList(
            NULL,   // cursor thread; always NULL to begin with
            0,      // mask to AND with Thread::m_State to filter returned threads 
            0);     // bits to match the result of the above AND.  (m_State & 0 == 0,
                    // so we won't filter out any threads)
        
        // Note that, by not filtering out any of the threads, we're intentionally including
        // stuff like TS_Dead or TS_Unstarted.  But that keeps us on the safe
        // side.  If an EE Thread object exists, we want to check its counters to be
        // absolutely certain it isn't executing in a profiler.

        while (pThread != NULL)
        {
            // Note that pThread is still in motion as we check its evacuation counter.
            // This is ok, because we've already changed the profiler status to
            // kProfStatusDetaching and flushed CPU buffers. So at this point the counter
            // will typically only go down to 0 (and not increment anymore), with one
            // small exception (below). So if we get a read of 0 below, the counter will
            // typically stay there. Specifically:
            //     * pThread is most likely not about to increment its evacuation counter
            //         from 0 to 1 because pThread sees that the status is
            //         kProfStatusDetaching.
            //     * Note that there is a small race where pThread might actually
            //         increment its evac counter from 0 to 1 (if it dirty-read the
            //         profiler status a tad too early), but that implies that when
            //         pThread rechecks the profiler status (clean read) then pThread
            //         will immediately decrement the evac counter back to 0 and avoid
            //         calling into the EEToProfInterfaceImpl pointer.
            // 
            // (see
            // code:ProfilingAPIUtility::InitializeProfiling#LoadUnloadCallbackSynchronization
            // for details)
            DWORD dwEvacCounter = pThread->GetProfilerEvacuationCounter();
            if (dwEvacCounter != 0)
            {
                LOG((
                    LF_CORPROF, 
                    LL_INFO100, 
                    "**PROF: Profiler not yet evacuated because OS Thread ID 0x%x has evac counter of %d (decimal).\n",
                    pThread->GetOSThreadId(),
                    dwEvacCounter));
                return FALSE;
            }

            pThread = ThreadStore::GetAllThreadList(pThread, 0, 0);
        }
    }

    // FUTURE: When rejit feature crew complete, add code to verify all rejitted
    // functions are fully reverted and off of all stacks.  If this is very easy to
    // verify (e.g., checking a single value), consider putting it above the loop
    // above so we can early-out quicker if rejitted code is still around.

    // We got this far without returning, so the profiler is fully evacuated
    return TRUE;
}

// ---------------------------------------------------------------------------------------
// After we've verified a detaching profiler has fully evacuated, call this to unload the
// profiler and clean up state.
//
// Assumptions:
//     Since this is called well after the profiler called RequestProfilerDetach, the
//     profiler must not have any other threads in use. Also, now that the profiler has
//     been evacuated, no CLR threads will be calling into the profiler (thus the
//     profiler will not gain control via CLR threads either). That means the profiler
//     may not call back into the CLR on any other threads.
//

// static
void ProfilingAPIDetach::UnloadProfiler()
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    _ASSERTE(g_profControlBlock.curProfStatus.Get() == kProfStatusDetaching);

    {
        CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst());

        // Notify profiler it's about to be unloaded
        _ASSERTE(s_profilerDetachInfo.m_pEEToProf != NULL);
        s_profilerDetachInfo.m_pEEToProf->ProfilerDetachSucceeded();

        // Reset detach state.
        s_profilerDetachInfo.Init();

        // This deletes the EEToProfInterfaceImpl object managing the detaching profiler,
        // releases the profiler's callback interfaces, unloads the profiler DLL, sets
        // the status to kProfStatusNone, and resets g_profControlBlock for use next time
        // a profiler tries to attach.
        // 
        // Note that s_profilerDetachInfo.Init() has already NULL'd out
        // s_profilerDetachInfo.m_pEEToProf, so we won't have a dangling pointer to the
        // EEToProfInterfaceImpl that's about to be destroyed.
        ProfilingAPIUtility::TerminateProfiling();
    }

    ProfilingAPIUtility::LogProfInfo(IDS_PROF_DETACH_COMPLETE);
}

// ----------------------------------------------------------------------------
// ProfilingAPIDetach::ProfilingAPIDetachThreadStart
//
// Description:
//    Thread proc for DetachThread. Serves as a simple try/catch wrapper around a call to
//    ProfilingAPIDetach::ExecuteEvacuationLoop.  This thread proc is specified by
//    code:ProfilingAPIDetach::CreateDetachThread when it spins up the new DetachThread.
//    This occurs when a profiler is either startup-loaded or attach-loaded.
//
// Arguments:
//    * LPVOID thread proc param is ignored
//
// Return Value:
//    Just returns 0 always.
//

// static
DWORD WINAPI ProfilingAPIDetach::ProfilingAPIDetachThreadStart(LPVOID)
{
    CONTRACTL
    {
        NOTHROW;
        GC_TRIGGERS;
        MODE_PREEMPTIVE;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    // At start of this thread, set its type so SOS !threads and anyone else knows who we
    // are.
    ClrFlsSetThreadType(ThreadType_ProfAPI_Detach);

    LOG((
        LF_CORPROF, 
        LL_INFO10, 
        "**PROF: DetachThread created and executing.\n"));

    // This try block is a last-ditch stop-gap to prevent an unhandled exception on the
    // DetachThread from bringing down the process.  Note that if the unhandled
    // exception is a terminal one, then hey, sure, let's tear everything down.  Also
    // note that any naughtiness in the profiler (e.g., throwing an exception from its
    // Initialize callback) should already be handled before we pop back to here, so this
    // is just being super paranoid.
    EX_TRY
    {
        // Don't care about return value, thread proc will just return 0 regardless
        ExecuteEvacuationLoop();
    }
    EX_CATCH
    {
        _ASSERTE(!"Unhandled exception on profiling API detach thread");
    }
    EX_END_CATCH(RethrowTerminalExceptions);

    LOG((
        LF_CORPROF, 
        LL_INFO10, 
        "**PROF: DetachThread exiting.\n"));

    return 0;
}

// ---------------------------------------------------------------------------------------
// Called during startup or attach load of a profiler to create a new thread to fill the role of
// the DetachThread.
//

// static
HRESULT ProfilingAPIDetach::CreateDetachThread()
{
    // This function is practically a leaf (though not quite), so keeping the contract
    // strict to allow for maximum flexibility on when this may called.
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_ANY;
        CANNOT_TAKE_LOCK;
    }
    CONTRACTL_END;

    // FUTURE: When reattach with neutered profilers is implemented, this
    // function should check if a DetachThread already exists (use synchronization
    // to prevent race), and just return if so.

    HandleHolder hDetachThread;

    // The DetachThread is intentionally not an EE Thread-object thread (it won't
    // execute managed code).
    hDetachThread = ::CreateThread(
        NULL,       // lpThreadAttributes; don't want child processes inheriting this handle
        0,          // dwStackSize (0 = use default)
        ProfilingAPIDetachThreadStart,
        NULL,       // lpParameter (none to pass)
        0,          // dwCreationFlags (0 = use default flags, start thread immediately)
        NULL        // lpThreadId (don't need therad ID)
        );
    if (hDetachThread == NULL)
    {
        DWORD dwErr = GetLastError();
        
        LOG((
            LF_CORPROF, 
            LL_ERROR, 
            "**PROF: Failed to create DetachThread.  GetLastError=%d.\n",
            dwErr));

        return HRESULT_FROM_WIN32(dwErr);
    }

    return S_OK;
}

//---------------------------------------------------------------------------------------
//
// Accessor for ProfilingAPIDetach::s_profilerDetachInfo.m_pEEToProf, which is the
// profiler being detached (or NULL if no profiler is being detached).
//
// Return Value:
//      EEToProfInterfaceImpl * for the profiler being detached.
//

// static
EEToProfInterfaceImpl * ProfilingAPIDetach::GetEEToProfPtr()
{
    LIMITED_METHOD_CONTRACT;
    return s_profilerDetachInfo.m_pEEToProf;
}

#endif // FEATURE_PROFAPI_ATTACH_DETACH