자바 어플리케이션을 개발하다 보면 외부 프로세스를 실행해야 할 경우가 있다.
예를 들어 자바 어플리케이션 내에서 서버의 특정 스크립트를 실행한다거나 다른 시스템 (spark, hadoop etc)에 명령을 내리는 경우다.
이번에 포스팅 할 내용은 JAVA process exec()명령을 실행하고 process.waitFor()를 실행함으로써 시스템이 행이 걸려 한참을 헤맨 내용이다.......정확히는 자바 어플리케이션 내부에서 Spark 어플리케이션을 실행하고 waitFor로 작업이 마무리 되었을 때 그 다음 작업을 진행시키는 배치가 맛이 가버린 내용이다.
자바내에서 특정 입력값을 사용자로 부터 받아 스파크(SPARK)의 특정 작업을 여러번 호출시키도록 설계되어 있다. admin시스템으로써 외부 부서에서 보통 3개의 입력값만 사용한다고 하셨었고 당연히 기능 개발후 테스트까지 마친 상태였다.
하지만 문제는 사용자가 5개의 입력값을 입력함으로써 시작되었다.....처음에는 Spark작업을 의심했었다. 다량의 Input값으로 메모리문제가 발생해 정상적인 아웃풋을 자바 어플리케이션에 전달하지 못하여 배치가 hang에 걸린 줄 알았었으나....그 문제는 아니였다.
[ 문제가 발생한 기존 코드 ]
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
보통 이렇게만 해도 큰 문제가 되지 않는다. 왜냐하면 보통 Command라고 해봤자 정말 단순한 명령어(grep, kill과 같은 단순한 리눅스 명령어)일 확률이 높기 때문이다.
하지만 이번 경우에는 달랐다.
문제는 명령어의 입력값이 커지면서 스파크(Spark) 작업이 여러번 돌게 되었고 작업이 끝나는 동안 outputstream을 한 번도 읽어주지 않음으로써 해당 버퍼가 꽉 차버리면서 정상적인 결과값을 읽지 읽어들이지 못하게 되면서 waitFor가 계속해서 끝나지 않는 상태가 되어 hang이 걸리게 되는 것이다.
[ 문제를 해결 코드 ]
가장 간단하게는 process의 outputstrem을 받아 close해주는 방식이다.
process.getOutputStream().close();
행에 걸린 경우는 별도의 에러코드도 내뱉지 않게 된다. 문제 발생시 에러에 대한 내용 파악이 필요하다면 process.getErrorStream() 을 받아 처리하는 부분의 구현이 필요하다.
관련해서 java.lang.Process 클래스의 API 문서에 다음과 같이 나와있다.
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.
간단히 말해 표준입력과 출력에 대한 버퍼 사이즈가 제한이 되어 있고, subprocess의 input stream과 또는 output stream을 즉시 읽지 못한다면 subprocess가 block되거나 심지어 deadlock에 빠질 수 있다는 것이다.
이렇게 나는 이런 버퍼관련 이슈와 관련 신경쓰지 않고 싶을 경우에는 Apache Commons Exec를 쓰면 된다.
좀 더 자세한 내용이 궁금하다면 아래의 참고문헌을 참고 바란다.
[ 참고 문헌 ]
https://d2.naver.com/helloworld/1113548
https://pasudo123.tistory.com/250
https://stackoverflow.com/questions/5483830/process-waitfor-never-returns