비동기식(Asynchronous) 프로그래밍 구조에서의 디버깅 문제 하나를 이전 글에서 소개했습니다. 오늘은 해당 문제의 원인이 무엇이었고 어떻게 코드를 수정했는지 정리해 보도록 하겠습니다.
→ 비동기식(Asynchronous) 프로그래밍 구조에서 디버깅 문제 (1)
아래는 제가 작성했던 버그 2가지가 있는 모듈의 Pseudoe 코드입니다. 두 코드 조각은 서로 다른 실행 흐름(쓰레드)을 가지고 서로 상호작용합니다. 송신부는 통신 채널로 명령을 전송하고 응답을 대기하고, 수신부는 응답을 수신한 후 송신부에 응답 수신을 알리는 역할을 합니다.
loop {
Message command = commandQueue.pop(); // 1
channel.write(command); // 2
SyncObject syncObj = new SyncObject(); // 3
syncMap.put(command.id, syncObj); // 4
if(!syncObj.wait(2000)) { // 5
throw new Exception("no response"); // 6
}
}
loop {
Message response= responseQueue.pop(); // 1
syncObj = syncMap.get(response.id); // 2
if (syncObj != null) { // 3
syncObj.signal(); // 4
syncMap.remove(response.id); // 5
}
pipeline.next(response); // 6
}
위 코드를 실행하면 간헐적으로 “no response” 예외가 발생합니다. 수신부에서 로그를 남겨보면 응답은 분명하게 수신되고 있습니다. 특이한 점은 수신부 코드에서 수신 이벤트 알람을 주기 위한 동기화 객체가 문제 시점에 null 값이라는 것입니다. 분석해 보니 동기화 객체가 간헐적으로 null 이 되는 이유는 두 가지가 있었습니다.
if (syncObj != null) { ... }
송신부와 수신부 코드가 실행될 것이라 예상한 흐름은 다음과 같았습니다. 제 머리 속에서는 잘 동작했습니다. 그러나 두 개의 실행 흐름이 어떻게 스위치 되면서 실행되는지 한줄씩 로그를 남기며 확인해 보았더니 전혀 예상치 못한 흐름으로 실행되는 케이스가 발생했습니다.
문제가 발생하는 시점에는 다음과 같이 절묘한 순서로 코드가 실행되고 있었습니다. 그래서 수신부에서 이전 명령에 대한 동기화 객체 삭제 시 다음 명령어의 동기화 객체를 의도치 않게 삭제하는 문제가 발생했고 실제 다음번 응답이 수신되면 동기화 객체는 null 인 문제가 발생했습니다.
또한 문제가 발생하는 시점에는 추가적으로 다음과 같은 절묘한 순서로 코드가 실행될 때도 있었습니다. 그래서 송신부에서 명령 전송 후에 동기화 객체 생성하고 Map 에 삽입하기도 전에 응답이 수신되어 수신부에서 동기화 객체가 null 인 문제가 발생하고 있었습니다.
크게 코드의 두 부분을 수정함으로 문제를 해결할 수 있었습니다.
loop {
Message command = commandQueue.pop();
SyncObject syncObj = new SyncObject(); // 명령 전송 전 동기화 객체 생성과 Map 삽입
syncMap.put(command.id, syncObj);
channel.write(command);
if(!syncObj.wait(2000)) {
throw new Exception("no response");
}
syncMap.remove(response.id); // 동기화 객체 제거
}
loop {
Message response= responseQueue.pop(); // 1
syncObj = syncMap.get(response.id); // 2
if (syncObj != null) { // 3
syncObj.signal(); // 4
}
pipeline.next(response); // 6
}
비동기적으로 수행되는 두 개의 실행 흐름에서 발생한 문제는 제 머리속에서 잘 그려지지 않았습니다. 코드를 한 줄 한 줄 디버깅하며 눈으로 보기 전에 전혀 예상이 되지 않았던 것 같습니다. 경험이 많아지면 이런 케이스들이 코드만 보고 눈에 보일까요? 여러분은 어떠셨나요? : ) 글로 옮기다 보니 글로 설명하기도 동기적으로 수행되는 코드를 설명하는 것 보다 훨씬 어려운 것 같습니다.
퀴즈 잘 읽었습니다