README 에 내 이름이 올라간 날
간혈적인 문제 발생
현장에서 연락이 왔습니다.
간헐적으로 처리를 못하고 화물이 리젝 라인으로 빠진다.
개발자는 이미 퇴사한 상태였고, 그동안의 기록을 살펴보았습니다. 혹시나 단서가 될 만한 것이 있을까 싶었습니다.
이미 이 이슈에 대해서는 여러 번 이야기가 올라왔던 것 같습니다. 당시의 판단으로는 인프라를 의심하고 있었습니다. 노이즈, 네트워크 장비, PLC 등.
저 역시 코드를 살펴보았습니다. PLC와 메시징하는 부분, 전달받은 메시지를 처리하는 부분의 로직은 아무리 봐도 문제가 있어 보이지 않았습니다. 로그에서도 특별한 점은 발견되지 않았습니다.
그러던 중 SuperSimpleTcp라는 라이브러리가 눈에 들어왔습니다.
소켓 통신을 주로 .NET에서 제공하는 기본 기능으로 다뤄왔던 터라 오히려 더 눈에 들어왔던 것 같습니다.
테스트를 해보자
똑같은 설정의 소켓 클라이언트와 서버를 만들고, 기존 핸들러 로직을 복사해 아주 단순한 테스트 환경을 만들어봤습니다.
그리고 중복된 메시지를 여러 번 전송해보았습니다.
정말 간헐적으로 Receive 핸들러에서 세그먼트 조립이 실패하고 있었습니다.
이전 기록을 보고 저 역시 내심 인프라 문제라고 생각하고 있었는데, 정작 문제는 소프트웨어에 있었습니다.
문제가 되는 지점은 찾았습니다.
하지만 조금 특이한점이 있었습니다.
왜 Thread 2개가 동시에 진입했지?
SuperSimpleTcp 라이브러리를 조금 더 깊게 살펴보았습니다.
멀티쓰레드구나!
SuperSimpleTcp 에서는 UseAsyncDataReceivedEvents 의 기본값이 true 였고 이로 인해 DataReceived 핸들러가 여러 스레드에서 동시에 실행될 수 있었습니다.
그렇다면 일단 해결 방법은 단순합니다. 해당 옵션을 끄면 됩니다.
하지만 조금 의아했습니다.
Socket 통신을 다룰 땐 세그먼트를 조립하는 로직이 절대 빠질수가 없습니다. 그리고 그 중간에 다른 스레드가 개입해서는 안 됩니다. 이 부분 만큼은 동기적으로 처리되야하는 부분입니다.
네트워크 버퍼를 아무리 크게 잡는다고 해도 한 번의 수신에서 메시지가 완성되어 온다는 보장은 없기 때문입니다.
다시 정리해보니 원인은 명확해졌습니다.
UseAsyncDataReceivedEvents의 기본값이 true였고 이로 인해 DataReceived 핸들러가 동시에 실행될 수 있었습니다.
그 과정에서 메시지 세그먼트를 조립하던 상태가 서로 섞였고 결과적으로 수신 데이터가 마치 노이즈처럼 보이는 문제가 발생했습니다.
조금 허탈했습니다. 노이즈로 판단되었을 때 로그라도 남아 있었다면 조금 더 쉽게 찾을 수 있었을 텐데.
어찌 됐든 원인은 파악했고 현장의 문제도 해결할 수 있었습니다.
이슈 등록
새벽 잠이안와 뒤척이고 있는데 문득 그때 사용했던 라이브러리와 문제가 생각났습니다. GitHub에 들어가 코드를 먼저 확인해보았습니다. 코드는 제가 당시 원인이라고 생각했던 부분이 그대로였습니다.
그러다 문득 Github Issue 로 남겨볼까? 하는 생각이 들었습니다. 누군가 저 처럼 고생하지 않기를 바라는 마음도 있었습니다.
지구 반대편 개발자
사실 등록하고 나서는 별 기대를 하지 않았습니다. 그저 혹시라도 다른 사람이 비슷한 문제를 겪고 있었다면 도움이 되겠다는 정도였습니다.

그런데 수시간 후 답변이 달렸습니다.
지구 반대편에 있는 개발자가 제가 작성한 Issue를 읽고 반영하였습니다.
그리고 v3.1.1 의 README 와 코드 주석에 제 이름이 적혀있었습니다.
간접적이지만 오픈소스에 기여를 했다는거에 무언가 기뻣습니다.
작은 흔적
제가 무엇을 한건 아닙니다.
코드를 직접 수정해서 PR 을 보낸것도 아니고 그저 이상한점을 발견하고 재현해보고 그 내용을 Issue 로 남겼을 뿐입니다.
그런데 그 작은 이슈 하나가 지구 반대편의 개발자에게 전달되고 실제 코드 변화로 이루어졌습니다. 그 과정이 즐거웠습니다.
언젠가는 저도 오픈소스 세상에서 누군가의 이슈에 답변을 남기는 개발자가 되고 싶습니다. 이번 경험이 그 첫걸음이 되면 좋겠습니다.