kbmMW 스케줄러 WaitRun 메서드의 데드락 문제

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을 반드시 사용해야 한다.

태그: Delphi kbmMW scheduler deadlock threading

7월 8일 20:36에 게시됨