├── .gitignore ├── README.md ├── build.gradle ├── connect-hang.sh ├── execute.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── ec2-list.png ├── hang1.png ├── hang2.png ├── hang3.png ├── oom.png ├── open-file-error.png ├── open-file1.png ├── open-file2.png ├── open-file3.png ├── open-file4.png ├── python0.png ├── python1.png ├── python2.png ├── python3.png ├── python4.png ├── python5.png ├── python6.png ├── python7.png ├── server-memory.png ├── thread1.png ├── ulimit-a1.png ├── ulimit-a2.png ├── ulimit-a3.png ├── ulimit-ah01.png ├── ulimit-ah02.png ├── ulimit-ah03.png ├── ulimit-ah04.png ├── ulimit-ah05.png ├── ulimit-ah06.png ├── ulimit-ah07.png ├── ulimit-ah08.png ├── 비교1.png └── 비교2.png ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── jojoldu │ │ └── thread │ │ ├── Application.java │ │ └── ThreadMarker.java └── resources │ └── application.yml └── test └── java └── com └── jojoldu └── thread └── ApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | /out 5 | nohup.out 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /build/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open files, max user processes 2 | 3 | > [회사 블로그](http://woowabros.github.io/experience/2018/04/17/linux-maxuserprocess-openfiles.html)에 올린글을 정리차 개인 블로그에도 정리합니다. 4 | 5 | Linux에서 open files, max user processes 설정에 대해 아는게 없어 정리하게 되었습니다. 6 | 팀에서 서버 작업하던 중, 쓰레드와 관련해서 문제가 발생했습니다. 7 | 제가 진행하던 일이 아니라서 옆에서 해결하는 과정을 지켜봤었는데요. 8 | (팀에 인프라 대장님이 계셔서 휘둥그레 하면서 봤습니다.) 9 | 10 | 11 | 부끄럽게도 **전혀 모르는 내용이 오고가서 복기 차원에서 공부하고 기록**합니다. 12 | 13 | 본문의 모든 내용은 테스트를 위해 임의로 생성한 코드들입니다. 14 | 모든 코드는 [Github](https://github.com/jojoldu/linux-thread)에 있으니 필요하시면 언제든지 사용하셔도 됩니다. 15 | 16 | ## 1. Max user processes 17 | 18 | Linux에는 OS 레벨에서의 제한 설정이 있습니다. 19 | 보통 이를 ```ulimit``` (user limit) 이란 명령어로 확인하는데요. 20 | 2가지 옵션으로 대부분 확인합니다. 21 | 22 | * ```ulimit -a``` 23 | * soft ulimit 24 | * ```ulimit -aH``` 25 | * hard ulimit 26 | 27 | 톰캣을 이용해서 서버 운영 도중, 다음과 같이 ```OutOfMemoryError```가 발생했다고 가정하겠습니다. 28 | 29 | ![oom](./images/oom.png) 30 | 31 | 더이상 쓰레드를 생성할 수 없다는 에러인데요. 32 | 뭐가 문제였는지 하나씩 확인해보겠습니다. 33 | 맨 처음 서버를 할당 받은 초기 상태 그대로라 ```ulimit -a```는 아래와 같습니다. 34 | 35 | ![ulimit-a1](./images/ulimit-a1.png) 36 | 37 | 화면을 보시면 ```open files``` 와 ```max user processes```의 값이 **1024로 동일하게** 잡혀있습니다. 38 | 쓰레드 생성에 문제가 발생한거라 ```max user processes```가 문제인것 같지만, 확신할 수 없으니 테스트 환경을 구축해서 실험해보겠습니다. 39 | 40 | 41 | 테스트용 서버는 AWS EC2의 **t2.micro**입니다. 42 | t2.micro로 생성후 ```ulimit -a```로 확인해보겠습니다. 43 | 44 | ![ulimit-a3](./images/ulimit-a2.png) 45 | 46 | t2.micro는 기본 설정이 ```open files```가 1024, ```max user processes```가 3902로 잡혀있다는 것을 알 수 있습니다. 47 | 48 | 자 그럼 간단하게 추측할 수 있는 것이, 현재 설정에서 ```1024 <= 동시에 생성가능한 쓰레드수 <= 3902```라면 ```max user processes```가 부족해서 발생한 문제임을 알 수 있겠죠? 49 | 이를 확인하기 위해 간단한 스프링부트 프로젝트를 생성하겠습니다. 50 | 51 | 코드는 간단합니다. ```/4000```으로 HTTP 요청이 오면 **비동기로 4천개의 쓰레드를 동시에 생성**하고, 20분간 유지합니다. 52 | 53 | ![ulimit-a3](./images/ulimit-a3.png) 54 | 55 | 프로젝트를 테스트용 EC2에 배포하고 ```curl```과 ```tail -f nohup```으로 확인해보겠습니다. 56 | 57 | ![thread1](./images/thread1.png) 58 | 59 | 사진속을 보시면 3855번째에서 ```unable to create new native thread``` 에러 메세지가 발생했습니다. 60 | 즉, open file 제한인 1024개를 초과해서 쓰레드가 생성된 것입니다! 61 | **max user processes만큼만 쓰레드가 생성**된것을 확인할 수 있습니다. 62 | 63 | > [Linux에서는 프로세스와 쓰레드를 동일하게 봅니다](https://stackoverflow.com/a/344292). 64 | 65 | ## 2. Open files 66 | 67 | 두번째로 위에 있던 open files 값은 어떤 값을 가리키는지 알아보겠습니다. 68 | [open files에 관해 검색](http://meetup.toast.com/posts/54)을 해보면 이 값이 **프로세스가 가질 수 있는 소켓 포함 파일 개수**를 나타낸다는 것을 알 수 있습니다. 69 | 70 | 자 그럼 **소켓을 open files 값 보다 많이 만들면** 어떻게 되는지 테스트 해보겠습니다. 71 | 테스트 방법은 간단합니다. 72 | 73 | * RestTemplate로 다른 서버로 API요청 (소켓 생성)을 보내고, 해당 서버에서는 20분간 응답을 대기 시킵니다. 74 | * 위 요청을 동시에 1100개를 보냅니다. 75 | * open files의 제한이 1024개이기 때문에 1100개가 발송되기전에 open files 관련된 에러가 발생하면 실험 성공! 76 | * Java, Spring을 실행시키면 기본적으로 몇개의 file이 오픈됩니다. 77 | * 이를 계산하면서 하기가 애매하니, 1100개를 보내면 기본 open된 파일 + 추가 생성된 파일을 합쳐서 1024개가 넘어가는 시점에 에러가 발생한다는 계산입니다. 78 | 79 | API 요청을 받아, 20분간 대기시켜줄 서버로 ec2를 한대 더 생성합니다. 80 | 81 | 테스트에 사용한 코드는 다음과 같습니다. 82 | 83 | **요청을 보낼 메소드** 84 | 85 | ![hang1](./images/hang1.png) 86 | 87 | * RestTemplate 타임아웃이 먼저 나면 안되기 때문에 타임아웃을 30분으로 지정합니다. 88 | 89 | **요청을 받을 메소드** 90 | 91 | ![hang2](./images/hang2.png) 92 | 93 | 자 그리고 **동시에 1100개의 요청**을 보낼수 있도록 간단한 비동기 요청 스크립트를 만듭니다. 94 | 95 | ![hang3](./images/hang3.png) 96 | 97 | (실제 운영환경에선 [Ngrinder](http://naver.github.io/ngrinder/)등을 통해서 하겠지만, 여기선 간단한 테스트이니 스크립트로 대체합니다.) 98 | 99 | 준비가 다 되었으니 한번 테스트를 진행해보겠습니다! 100 | 101 | ..... 102 | 103 | 테스트를 진행하자마자 바로 에러가 발생했습니다. 104 | 로그를 확인해보니 105 | 106 | ![server-memory](./images/server-memory.png) 107 | 108 | 단순 쓰레드 생성과 달리, **EC2의 서버 메모리가 먼저 부족**해져서 EC2 사양을 높여서 다시 실험하겠습니다. 109 | 110 | ![ec2-list](./images/ec2-list.png) 111 | 112 | (t2.micro는 메모리가 1GB인지라, t2.large (8GB) 로 변경합니다.) 113 | 114 | 업데이트된 EC2의 ```ulimit```은 아래와 같습니다. 115 | 116 | ![비교1](./images/비교1.png) 117 | 118 | ![비교2](./images/비교2.png) 119 | 120 | > AWS EC2의 사양을 올릴수록 ```max user processes```가 적절한 값으로 증가하는 것을 확인할 수 있습니다. 121 | 이를 통해 알 수 있는 것은, AWS EC2를 사용하실 경우 ```max user processes``` AWS 내부에서 인스턴스 사양에 맞게 적절한 값을 세팅해주니, 굳이 저희가 손댈필요는 없다는 것입니다. 122 | 123 | 자 그럼 다시 한번 테스트를 해보겠습니다. 124 | 테스트용 서버에 프로젝트를 배포하고, 해당 프로젝트가 생성한 open file count를 아래 명령어로 확인합니다. 125 | 126 | ```bash 127 | ls -l /proc/$PID/fd | wc -l 128 | ``` 129 | 130 | ![open-file1](./images/open-file1.png) 131 | 132 | PID를 찾고, 133 | 134 | ![open-file2](./images/open-file2.png) 135 | 136 | 생성된 open file 리스트를 확인할 수 있습니다. 137 | 준비가 다 되었으니, 1100개 요청을 보내는 스크립트를 실행해봅니다! 138 | 139 | ![ulimit-ah01](./images/ulimit-ah01.png) 140 | 141 | 엇? 142 | 결과가 뭔가 이상합니다. 143 | 분명 ```open files```의 값은 1024개로 되어있는데 1330개가 열려있다고 나오다니요. 144 | 145 | 이상하다는 생각에 스크립트를 몇번 더 실행합니다. 146 | (한번 실행때마다 1100개의 요청이 간다고 생각하시면 됩니다.) 147 | 148 | ![ulimit-ah02](./images/ulimit-ah02.png) 149 | 150 | 2번을 추가로 더 실행해서 **3300개의 요청이 갔음에도 에러없이 처리**되고 있습니다. 151 | 언제 터지는지 확인하기 위해 추가로 더 요청해봅니다 (총 4400개가 갑니다.) 152 | 153 | ![ulimit-ah03](./images/ulimit-ah03.png) 154 | 155 | 드디어 Open File 에러가 발생했습니다! 156 | 보시면 4097개까지만 열린채로 에러가 발생한 것을 알 수 있는데요. 157 | **이전 요청이 3532개를 생성했으니 추가로 1100개를 요청하면 4600개 이상이 생성되어야**하는데 왜 4097개까지만 생성된 것인지 궁금합니다. 158 | 159 | 혹시나 하는 마음에 ```ulimit -aH```로 soft가 아닌, hard옵션을 확인해봅니다. 160 | 161 | ![ulimit-ah04](./images/ulimit-ah04.png) 162 | 163 | 오? 164 | 마침 딱 4096개 입니다! 165 | 프로세스별 open file은 **soft 값이 아닌 hard 값까지 생성**가능한게 아닐까? 라는 추측이 됩니다. 166 | 검증하기 위해 hard의 open files를 **5120**개로 증가시킵니다. 167 | (soft 옵션은 그대로 1024개 입니다.) 168 | 169 | ![ulimit-ah05](./images/ulimit-ah05.png) 170 | 171 | 자 그리고 다시 요청을 진행해보겠습니다. 172 | 이번에 4096개가 넘는 요청이 가능해진다면 soft가 아닌, hard 값까지 생성 가능하다는걸 알수있겠죠? 173 | 174 | ![ulimit-ah06](./images/ulimit-ah06.png) 175 | 176 | 예상대로 4000개가 넘는 API 요청 (4635)이 가능해졌습니다! 177 | 178 | 실제로 ```ulimit -a```로 확인할 수 있는 **soft 값으로 소켓 생성이 제한되지는 않는다**는 것을 알 수 있습니다. 179 | 결국 **소켓 생성 제한은 hard 옵션에 따라간다** 라는 이야기가 됩니다.... 180 | 181 | ![python0](./images/python0.png) 182 | 183 | 이 글을 마무리하려던중, 깜짝 놀랄 이야기를 들었습니다. 184 | 185 | > 파이썬은 안그러는데요? 186 | 187 | ### 추가 188 | 189 | 제보를 해주신 배민찬의 모 선임님과 함께 확인을 해봤습니다. 190 | soft limit으로 1024, hard limit으로 4096인걸 확인하고, **파이썬 스크립트로 file을 임의로** 열어봅니다. 191 | 192 | ![python1](./images/python1.png) 193 | 194 | > 여기서 3개를 빼야하는 이유는 stdin, stdout, stderr의 표준 입/출력이 포함됐기 때문입니다. 195 | 196 | 1021개에서 1개를 더 추가하니 바로 ```Too many open files``` Error가 발생합니다! 197 | 헉? soft 옵션이 파이썬에서 잘 적용된걸까요? 198 | 199 | 진짜 그런지 한번 더 확인해봅니다. 200 | open files soft 값을 2000으로 증가시킨후 다시 1997개가 넘는 file을 open 해봅니다. 201 | 202 | ![python2](./images/python2.png) 203 | 204 | 여기서도 마찬가지로 1997개 이상 file open시에 바로 ```Too many open files``` Error가 발생합니다. 205 | 206 | **왜 파이썬은 soft옵션까지만 file이 오픈되고, Java에선 hard 옵션까지 file이 오픈되는건지** 이상했습니다. 207 | 208 | 이상하단 생각에 ```strace```로 JVM 로그를 확인해보니! 209 | 210 | ![python4](./images/python4.png) 211 | 212 | 이렇게 ```setrlimit```으로 limit을 업데이트하는 로그가 찍혀있습니다! 213 | 214 | 왜 이런 로그가 발생했는지 [오라클 Java 옵션](http://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html)을 찾아봤습니다. 215 | 문서에는 ```MaxFDLimit``` 라는 옵션이 있었는데요, 뭔가 file limit과 관련돼 보입니다. 216 | 217 | ![python5](./images/python5.png) 218 | 219 | 이 옵션이 뭔지 찾아보니 [openjdk](https://github.com/dmlloyd/openjdk/blob/c3f27ada97987466e9c6e33e02e676bd69b78664/src/hotspot/os/linux/os_linux.cpp#L4998) 코드에서 **이 옵션이 true일 경우 ```setlimit``` 으로 limit을 증가**시키는 것을 확인할 수 있습니다. 220 | 221 | ![python6](./images/python6.png) 222 | 223 | 그리고 설치된 Java의 ```MaxFDLimit``` **기본값이 true**임을 확인할 수 있습니다. 224 | 225 | ![python7](./images/python7.png) 226 | 227 | 즉, **리눅스 OS에서 JDK 실행시 자동으로 limit 사이즈를 증가**시켜준다는 것을 알 수 있습니다. 228 | 229 | ## 결론 230 | 231 | 위 2개의 실험으로 얻은 결론입니다. 232 | 233 | * Java에서 동시에 생성 가능한 쓰레드 수는 ```max user processes```를 따라간다. 234 | * Java에서 소켓 통신(HTTP API, JDBC 커넥션 등)은 ```open file``` 옵션을 따라간다. 235 | * 단, JDK 내부 코드상에서 hard limit이 soft limit에 적용된다. 236 | 237 | 238 | > 참고로 Tomcat은 8 버전부터 기본 Connector 방식을 NIO로 사용합니다. 239 | (7 버전까지는 BIO) 240 | 그러다보니 maxConnections은 10,000, maxThreads는 200이 기본값입니다. 241 | (BIO에서는 둘의 값이 동일해야 합니다) 242 | 이번 테스트에서 사용되는 connection 수가 1만을 넘지 않기 때문에 기본옵션으로 진행했습니다. 243 | 244 | ## 번외) limit 옵션 설정방법 245 | 246 | 보통 soft limit과 hard limit을 별도로 관리하진 않습니다. 247 | 둘의 값을 동일하게 적용하는데요. 248 | 서버의 open files, max user processes등 옵션을 permanent (영구) 적용하기 위해선 ```/etc/security/limits.conf``` 을 수정하면 됩니다. 249 | 250 | ![ulimit-ah07](./images/ulimit-ah07.png) 251 | 252 | 맨 앞의 ```*```가 있는 자리는 사용자 계정을 나타냅니다. 253 | ec2-user로 지정하시면 ec2-user 계정에서만 옵션이 적용되는데요. 254 | 이미지처럼 ```*```로 지정하시면 **모든 사용자 계정**에 옵션이 적용됩니다. 255 | 256 | 이렇게 적용후, 다시 접속해서 확인해보시면! 257 | 258 | ![ulimit-ah08](./images/ulimit-ah08.png) 259 | 260 | 옵션이 잘 적용되어있음을 확인할 수 있습니다. 261 | 262 | ## 참고 263 | 264 | * [호스트웨이 ulimit 설정관련](http://faq.hostway.co.kr/Linux_ETC/7179) 265 | * [리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기](http://meetup.toast.com/posts/53) -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.0.1.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'org.springframework.boot' 16 | apply plugin: 'io.spring.dependency-management' 17 | 18 | group = 'com.jojoldu' 19 | version = '0.0.1-SNAPSHOT' 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | 26 | 27 | dependencies { 28 | compile('org.springframework.boot:spring-boot-starter-web') 29 | testCompile('org.springframework.boot:spring-boot-starter-test') 30 | } 31 | -------------------------------------------------------------------------------- /connect-hang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for num in {0..1100} 4 | do 5 | curl localhost:8080/connect-hang?index=$num & 6 | done 7 | -------------------------------------------------------------------------------- /execute.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_PID=$(pgrep -f linux-thread) 4 | kill -9 $CURRENT_PID 5 | 6 | /home/ec2-user/linux-thread/gradlew clean build 7 | 8 | nohup java -jar /home/ec2-user/linux-thread/build/libs/linux-thread-0.0.1-SNAPSHOT.jar & 9 | 10 | echo $(pgrep -f linux-thread) 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 06 12:27:20 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /images/ec2-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ec2-list.png -------------------------------------------------------------------------------- /images/hang1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/hang1.png -------------------------------------------------------------------------------- /images/hang2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/hang2.png -------------------------------------------------------------------------------- /images/hang3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/hang3.png -------------------------------------------------------------------------------- /images/oom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/oom.png -------------------------------------------------------------------------------- /images/open-file-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/open-file-error.png -------------------------------------------------------------------------------- /images/open-file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/open-file1.png -------------------------------------------------------------------------------- /images/open-file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/open-file2.png -------------------------------------------------------------------------------- /images/open-file3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/open-file3.png -------------------------------------------------------------------------------- /images/open-file4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/open-file4.png -------------------------------------------------------------------------------- /images/python0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python0.png -------------------------------------------------------------------------------- /images/python1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python1.png -------------------------------------------------------------------------------- /images/python2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python2.png -------------------------------------------------------------------------------- /images/python3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python3.png -------------------------------------------------------------------------------- /images/python4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python4.png -------------------------------------------------------------------------------- /images/python5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python5.png -------------------------------------------------------------------------------- /images/python6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python6.png -------------------------------------------------------------------------------- /images/python7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/python7.png -------------------------------------------------------------------------------- /images/server-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/server-memory.png -------------------------------------------------------------------------------- /images/thread1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/thread1.png -------------------------------------------------------------------------------- /images/ulimit-a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-a1.png -------------------------------------------------------------------------------- /images/ulimit-a2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-a2.png -------------------------------------------------------------------------------- /images/ulimit-a3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-a3.png -------------------------------------------------------------------------------- /images/ulimit-ah01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah01.png -------------------------------------------------------------------------------- /images/ulimit-ah02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah02.png -------------------------------------------------------------------------------- /images/ulimit-ah03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah03.png -------------------------------------------------------------------------------- /images/ulimit-ah04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah04.png -------------------------------------------------------------------------------- /images/ulimit-ah05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah05.png -------------------------------------------------------------------------------- /images/ulimit-ah06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah06.png -------------------------------------------------------------------------------- /images/ulimit-ah07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah07.png -------------------------------------------------------------------------------- /images/ulimit-ah08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/ulimit-ah08.png -------------------------------------------------------------------------------- /images/비교1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/비교1.png -------------------------------------------------------------------------------- /images/비교2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/images/비교2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'linux-thread' 2 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/thread/Application.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.thread; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.time.LocalDateTime; 13 | import java.time.format.DateTimeFormatter; 14 | 15 | @EnableAsync 16 | @RestController 17 | @SpringBootApplication 18 | public class Application { 19 | 20 | private ThreadMarker threadMarker; 21 | 22 | public Application(ThreadMarker threadMarker) { 23 | this.threadMarker = threadMarker; 24 | } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(Application.class, args); 28 | } 29 | 30 | @GetMapping("/connect-hang") 31 | public String connectHang(@RequestParam(value = "index") String index) { 32 | RestTemplate restTemplate = new RestTemplateBuilder() 33 | .setConnectTimeout(30 * 60 * 1000) // 30분 34 | .setReadTimeout(30 * 60 * 1000) // 30분 35 | .build(); 36 | 37 | System.out.println("index: " +index); 38 | 39 | return restTemplate.getForObject("http://ec2-13-125-219-239.ap-northeast-2.compute.amazonaws.com:8080/receive-hang?index="+index, String.class); 40 | } 41 | 42 | @GetMapping("/receive-hang") 43 | public String receiveHang(@RequestParam(value = "index") String index) throws InterruptedException { 44 | System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"))+" index: "+index); 45 | Thread.sleep(12000000); // 1200초 == 20분 46 | 47 | return "receiveHang"; 48 | } 49 | 50 | @GetMapping("/") 51 | public String load() { 52 | return "load"; 53 | } 54 | 55 | @GetMapping("/1100") 56 | public String go1100() { 57 | for (int i = 0; i < 1100; i++) { 58 | System.out.println("i: "+i); 59 | threadMarker.create(); 60 | } 61 | return "go1100!"; 62 | } 63 | 64 | @GetMapping("/4000") 65 | public String go4000() { 66 | for (int i = 0; i < 4000; i++) { 67 | System.out.println("i: "+i); 68 | threadMarker.create(); 69 | } 70 | return "go4000!"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/thread/ThreadMarker.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.thread; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * Created by jojoldu@gmail.com on 2018. 4. 13. 8 | * Blog : http://jojoldu.tistory.com 9 | * Github : https://github.com/jojoldu 10 | */ 11 | 12 | @Component 13 | public class ThreadMarker { 14 | 15 | @Async 16 | public void create() { 17 | System.out.println(Thread.currentThread()); 18 | 19 | try { 20 | Thread.sleep(12000000); //1200초 == 20분 21 | } catch (InterruptedException e) { 22 | e.printStackTrace(); 23 | } 24 | } 25 | 26 | @Async 27 | public void hang() { 28 | System.out.println("hang: "+Thread.currentThread()); 29 | 30 | try { 31 | Thread.sleep(12000000); //1200초 == 20분 32 | } catch (InterruptedException e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/linux-thread/80fb7a24dcf07d93dbc34ec71e40236c9538f163/src/main/resources/application.yml -------------------------------------------------------------------------------- /src/test/java/com/jojoldu/thread/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.thread; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------