43. JIT 활성 수명 축소와 객체 조기 수집 (GC.KeepAlive / SafeHandle)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// [주제] JIT 의 활성 수명(liveness) 축소와 객체 조기 수집 — GC.KeepAlive / SafeHandle
//
// 게임서버에서 네이티브 압축 라이브러리를 얇게 감싼 래퍼다. 핸들을 정수로
// 들고 있다가 파이널라이저에서 닫는다. 아래 프로그램의 출력을 예측하고
// 내부 동작으로 설명하라.
//
// [과제]
// 1) Run1 에서 무슨 일이 일어나는가? "그 인스턴스의 메서드가 아직 실행 중인데
// 인스턴스가 수집될 수 있는가"에 답하고, 관리 객체의 수명이 무엇으로
// 결정되는지 설명하라.
// 2) Run2 는 왜 Run1 과 다른가? GC.KeepAlive 의 실제 구현은 무엇이며,
// 아무 일도 하지 않는 메서드 호출이 어떻게 수명을 늘리는가?
// 3) Run3 이 이 문제를 구조적으로 푸는 방식을 설명하라. SafeHandle 의 참조
// 카운트와 P/Invoke 마샬링이 각각 무엇을 보장하는가?
// 4) Run4 의 출력과, 부활(resurrection)한 객체의 이후 운명을 설명하라.
// 파이널라이저는 어느 스레드에서 언제 도는가?
// 5) Debug 빌드나 디버거를 붙이면 Run1 의 결과가 달라질 수 있다. 이유는?
// 워크스테이션 GC 와 서버 GC 에서는 어떤 차이가 있는가?
// 6) 이 버그가 게임서버에서 어떤 증상으로 나타나는지(소켓 래퍼, 풀링된
// 네이티브 버퍼, 압축 컨텍스트) 서술하고 올바른 설계를 제시하라.
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
public static class NativeLib // 네이티브 라이브러리 흉내
{
private static readonly ConcurrentDictionary<int, bool> Alive = new();
private static int _next;
public static int Open() { int h = Interlocked.Increment(ref _next); Alive[h] = true; return h; }
public static void Close(int h)
{
Alive[h] = false;
Console.WriteLine($" [native] close(handle={h})");
}
public static int Compress(int h, int size)
{
if (!Alive.TryGetValue(h, out bool ok) || !ok)
throw new InvalidOperationException($"handle {h} is already closed");
return size / 2;
}
public static void Collect() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }
}
public sealed class NaiveCompressor // (1) 정수 핸들 + 파이널라이저
{
private readonly int _handle;
public NaiveCompressor() => _handle = NativeLib.Open();
~NaiveCompressor() => NativeLib.Close(_handle);
public int Compress(byte[] payload)
{
int h = _handle; // this 의 마지막 사용
NativeLib.Collect(); // 긴 작업 도중 GC 가 일어난다고 가정
return NativeLib.Compress(h, payload.Length);
}
public int CompressKeepAlive(byte[] payload)
{
int h = _handle;
NativeLib.Collect();
int result = NativeLib.Compress(h, payload.Length);
GC.KeepAlive(this);
return result;
}
}
public sealed class CompressHandle : SafeHandle // (2) SafeHandle 기반
{
public CompressHandle() : base(IntPtr.Zero, ownsHandle: true)
=> SetHandle(new IntPtr(NativeLib.Open()));
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle() { NativeLib.Close(handle.ToInt32()); return true; }
public int Compress(int size)
{
bool added = false;
try
{
DangerousAddRef(ref added);
NativeLib.Collect();
return NativeLib.Compress(handle.ToInt32(), size);
}
finally { if (added) DangerousRelease(); }
}
}
public sealed class PooledBuffer // (3) 부활
{
public static PooledBuffer Revived;
public readonly int Id;
public PooledBuffer(int id) => Id = id;
~PooledBuffer()
{
Console.WriteLine($" [finalizer] thread={Environment.CurrentManagedThreadId} id={Id}");
Revived = this; // 자기 자신을 정적 필드에 대입
}
}
public static class Program
{
private static readonly byte[] Payload = new byte[1024];
private static void RunNaive()
=> Console.WriteLine($" result={new NaiveCompressor().Compress(Payload)}");
private static void RunKeepAlive()
=> Console.WriteLine($" result={new NaiveCompressor().CompressKeepAlive(Payload)}");
private static void RunSafeHandle()
{
var h = new CompressHandle();
Console.WriteLine($" result={h.Compress(Payload.Length)}");
GC.KeepAlive(h);
}
private static void RunResurrect()
{
new PooledBuffer(7);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine($" Revived is null? {PooledBuffer.Revived is null}");
PooledBuffer.Revived = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(" second collect done");
}
public static void Main()
{
var runs = new (string Name, Action Run)[]
{
("Run1 naive", RunNaive), ("Run2 keepalive", RunKeepAlive),
("Run3 safehandle", RunSafeHandle), ("Run4 resurrect", RunResurrect),
};
foreach (var (name, run) in runs)
{
Console.WriteLine(name);
try { run(); }
catch (Exception ex) { Console.WriteLine($" EXCEPTION: {ex.GetType().Name}: {ex.Message}"); }
}
}
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.