kbmMW 스케줄러의 WaitRun 메서드에서 발생하는 데드락 문제를 재현하기 위해 테스트 코드를 작성했다. 이 문제는 두 가지 상황에서 발생할 수 있다.
첫 번째 상황은 Run 메서드 내부에서 예외가 발생하는 경우다. 이때 메인 스레드에서 실행 중인 ev.WaitRun이 무한 루프에 빠진다. 두 번째 상황은 AfterRun 콜백을 사용하는 경우다. 이 또한 동일한 문제를 야기한다. 해당 문제를 확인하기 위해 테스트 프로젝트를 구성했으며, 개발자에게 피드백을 전달했다. 다음 버전에서 수정이 기대된다.
이 문제는 상당히 심각하다. 개발자의 코드 내에서 WaitRun이 여러 곳에서 사용되고 있기 때문이다. 필자는 파일 비교 기능을 사용하면서 이 문제를 발견했다.
테스트 코드 - 예외 발생 시나리오
procedure TMainForm.btnTestExceptionClick(Sender: TObject);
var
ScheduledEvent: IkbmMWScheduledEvent;
begin
ScheduledEvent := Scheduler.Run(
procedure(const EventContext: IkbmMWScheduledEvent)
begin
raise Exception.Create('테스트 예외 메시지');
end)
.WhenException(
procedure(const EventContext: IkbmMWScheduledEvent; const Error: Exception)
begin
TThread.Synchronize(nil,
procedure
begin
lblStatus.Caption := '예외 발생: ' + Error.Message;
lblStatus.Refresh;
end);
end)
.Precise
.Activate;
try
// 실행 로직
finally
ScheduledEvent.WaitRun; // 무한 대기 발생
end;
ShowMessage('스케줄된 작업이 완료되었습니다!'); // 여기에 도달할 수 없음
end;
테스트 코드 - AfterRun 시나리오
procedure TMainForm.btnTestAfterRunClick(Sender: TObject);
var
ScheduledEvent: IkbmMWScheduledEvent;
begin
ScheduledEvent := Scheduler.Run(
procedure(const EventContext: IkbmMWScheduledEvent)
begin
Sleep(1000);
end)
.AfterRun(
procedure
begin
TThread.Synchronize(nil,
procedure
begin
lblStatus.Caption := 'AfterRun 실행됨';
lblStatus.Refresh;
end);
end)
.Precise
.Activate;
try
// 실행 로직
finally
ScheduledEvent.WaitRun; // 무한 대기 발생
end;
ShowMessage('스케줄된 작업이 완료되었습니다!'); // 여기에 도달할 수 없음
end;
해결 방법
2020년 6월: kbmMW 5.12 버전에서 이 문제가 해결되었다. 그러나 AfterRun을 사용하는 경우, 다음처럼 SynchronizedAfterRun으로 대체해야 한다.
procedure TMainForm.btnFixedAfterRunClick(Sender: TObject);
var
ScheduledEvent: IkbmMWScheduledEvent;
begin
ScheduledEvent := Scheduler.Run(
procedure(const EventContext: IkbmMWScheduledEvent)
begin
Sleep(1000);
end)
.SynchronizedAfterRun(
procedure
begin
lblStatus.Caption := 'AfterRun 실행됨';
lblStatus.Refresh;
end)
.Precise
.Activate;
try
// 실행 로직
finally
ScheduledEvent.WaitRun;
end;
ShowMessage('스케줄된 작업이 완료되었습니다!');
end;
추가 확인
2024년 3월 확인 결과, 현재까지 이 해결 방법이 유효하다. AfterRun 대신 SynchronizedAfterRun을 반드시 사용해야 한다.