Skip to content

Commit ac6fbb9

Browse files
[autofix.ci] apply automated fixes
1 parent b091152 commit ac6fbb9

12 files changed

+81
-75
lines changed

fundamentals/debug/pages/contribute/android/android_react_native_bundle_loading_sigbus_crash_debug.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<br/>
44
<ContributorHeader name="김희철" githubUrl="https://github.com/heecheolman" avatar="https://ca.slack-edge.com/E01JAGTHP8R-U01JVCVAP41-ea9f13e55dd5-512" />
55

6-
76
## 진단하기
87

98
Android에서 React Native 번들을 불러오는 과정에서 앱이 크래시되는 현상이 발생했어요. 에러
@@ -14,17 +13,19 @@ Android에서 React Native 번들을 불러오는 과정에서 앱이 크래시
1413
토스 앱에서는 React Native 번들을 효율적으로 관리하기 위해 캐시 시스템을 사용하고 있어요:
1514

1615
- 번들 캐시는 `cache/toss_react_bundle_cache` 디렉토리에 저장돼요
17-
- 예: `cache/toss_react_bundle_cache/insurance-ads`
16+
- 예: `cache/toss_react_bundle_cache/insurance-ads`
1817
- 실행 가능한(실행 중인) 번들은 `cache/toss_react_bundle_cache/execute/*` 에 저장돼요
19-
- 예: `cache/toss_react_bundle_cache/execute/insurance-ads`
18+
- 예: `cache/toss_react_bundle_cache/execute/insurance-ads`
2019

2120
### 번들 캐시 동작 방식
2221

2322
1. 로컬에서 번들 찾기 및 검증 (`maxAge`, 시그니처 검증)
2423
2. 검증 성공 시 캐시 디렉토리에서 `execute` 디렉토리로 복사
2524
3. 백그라운드에서 `remote` 번들 확인
25+
2626
- `304 not modified`면 메타 파일만 업데이트
2727
- 수정되었다면 `.pending_activation suffix`로 저장
28+
2829
4. 특정 조건이 되면 `.pending_activation` 파일을 `active` 상태로 전환
2930

3031
문제는 이 과정에서 이미 실행되고 있는 번들 파일에 동시에 write 작업이 발생하면서 `SIGBUS`
@@ -52,7 +53,7 @@ Android 버그 리포트에서 발생한 userNo를 키바나(토스의 로그
5253
- `maxAge가` 0이어서 항상 remote에서 번들을 받고 있었어요
5354
- `verify` 로직에서 `maxAge`가 0이면 항상 `null`을 반환하고, 그래서 항상 remote에서 불러오게 돼요
5455
- 초기 설계에서는 하루 캐시가 기본값이었고, 튜바 변수 `reactNative.scheme.maxage`를 참조해야
55-
했어요
56+
했어요
5657
- 그런데 언젠가부터 이 튜바 변수가 사라져서 `maxAge`가 0으로 설정되고 있었어요
5758

5859
## 수정하기
@@ -92,4 +93,4 @@ execFile.write(...)
9293

9394
파일 이름을 다르게 하거나 물리적으로 격리시켰을 때 크래시가 발생하지 않는다는 것을 확인하여,
9495
동시성 이슈임을 명확히 파악할 수 있었어요. 이처럼 문제를 격리시켜보는 실험은 원인을 빠르게
95-
좁혀나가는 데 유용한 기법이에요.
96+
좁혀나가는 데 유용한 기법이에요.

fundamentals/debug/pages/contribute/compile/codegen_callstack_overflow_debug.md

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
<br/>
44
<ContributorHeader name="윤민석" githubUrl="https://github.com/black7375" avatar="https://ca.slack-edge.com/E01JAGTHP8R-U085U98PY31-d26c07fac97e-512" />
55

6-
76
## 진단하기
87

98
TDS Desktop 마이그레이션 작업 중, `Codegen`으로 자동 생성된 결과로 매우 긴 코드를 작성했더니
109
파싱 콜스택이 터지는 문제가 발생했어요. ESLint와 TypeScript 컴파일러(tsc)가 모두 동작하지 않았고, 빌드조차 실패하는 상황이었어요.
1110

1211
### 문제의 심각성
12+
1313
- 로컬 개발 환경에서 ESLint 실행 불가
1414
- TypeScript 컴파일 실패
1515
- CI 빌드 파이프라인도 동일한 에러로 실패
@@ -28,18 +28,18 @@ TDS Desktop 마이그레이션 작업 중, `Codegen`으로 자동 생성된 결
2828
```jsx
2929
// 문제가 되는 코드 (560개 체이닝)
3030
const result = builder
31-
.method1()
32-
.method2()
33-
.method3()
34-
// ... (560개 계속)
35-
.method560();
31+
.method1()
32+
.method2()
33+
.method3()
34+
// ... (560개 계속)
35+
.method560();
3636
```
3737

3838
이런 매우 긴 체이닝 코드를 ESLint와 TypeScript가 파싱하려고 할 때, AST를 생성하는 과정에서
3939
재귀 호출이 너무 깊어져서 JavaScript 엔진의 콜스택 제한에 도달한 거예요.
4040

41-
4241
### 재현 조건
42+
4343
- Codegen으로 자동 생성된 대량의 메서드 체이닝 (560개)
4444
- ESLint 실행 시
4545
- TypeScript 컴파일 시
@@ -52,38 +52,38 @@ const result = builder
5252
```jsx
5353
// 수정 전: 560개를 한 줄로 체이닝 (콜스택 오버플로우)
5454
const result = builder
55-
.method1()
56-
.method2()
57-
// ... (560개)
58-
.method560();
55+
.method1()
56+
.method2()
57+
// ... (560개)
58+
.method560();
5959

6060
// 수정 후: 5개의 변수로 분할 (각각 약 100개씩)
6161
const step1 = builder
62-
.method1()
63-
.method2()
64-
// ... (100개)
65-
.method100();
62+
.method1()
63+
.method2()
64+
// ... (100개)
65+
.method100();
6666

6767
const step2 = step1
68-
.method101()
69-
.method102()
70-
// ... (100개)
71-
.method200();
68+
.method101()
69+
.method102()
70+
// ... (100개)
71+
.method200();
7272

7373
const step3 = step2
74-
.method201()
75-
// ... (100개)
76-
.method300();
74+
.method201()
75+
// ... (100개)
76+
.method300();
7777

7878
const step4 = step3
79-
.method301()
80-
// ... (100개)
81-
.method400();
79+
.method301()
80+
// ... (100개)
81+
.method400();
8282

8383
const result = step4
84-
.method401()
85-
// ... (160개)
86-
.method560();
84+
.method401()
85+
// ... (160개)
86+
.method560();
8787
```
8888

8989
이렇게 변수를 5개로 나누어서 각각 100여개씩 할당하고 이어가는 방식으로 변경하니, ESLint와
@@ -103,4 +103,4 @@ TypeScript 컴파일러가 정상적으로 동작했어요.
103103
빌더 패턴의 메서드 체이닝은 가독성과 편의성을 제공하지만, 무한정 길어질 수 있는 것은
104104
아니에요. JavaScript/TypeScript의 파서는 `AST`를 생성할 때 재귀적으로 동작하기 때문에, 너무
105105
깊은 체이닝은 콜스택 한계에 도달할 수 있어요. "현대 컴퓨터가 참 나약하다"고 농담할 수 있지만,
106-
실제로는 설계상의 한계예요.
106+
실제로는 설계상의 한계예요.

fundamentals/debug/pages/contribute/cursor/cursor_biome_formatting_debug.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<br/>
44
<ContributorHeader name="박찬혁" githubUrl="https://github.com/okinawaa" avatar="https://ca.slack-edge.com/E01JAGTHP8R-U06H69MRQMN-07d1cde6565c-512" />
55

6-
76
## 진단하기
87

98
추석 이후로 Cursor에서 Biome 자동 포맷팅이 갑자기 동작하지 않는 문제가 발생했어요. 코드를
@@ -15,11 +14,14 @@ Biome extension의 Output 로그를 확인해보니 플러그인 버전에 따
1514
있었어요:
1615

1716
- 최신 플러그인 사용 시:
17+
1818
```
1919
Cannot unshim Biome binary at "/Users/chanhyuk.park/Desktop/toss/toss-frontend/biome"
2020
because it is version 1.x.x. Please update to version 2 or higher.
2121
```
22+
2223
- 구버전 플러그인 사용 시:
24+
2325
```
2426
Unable to find the Biome binary.
2527
```
@@ -35,9 +37,12 @@ Unable to find the Biome binary.
3537
2. 구글링 → 유의미한 자료를 찾기 어려움
3638
3. GitHub 이슈 검색 → 비슷한 사례 찾기
3739
4. 플러그인 수동 설치 시도
40+
3841
- 플러그인이 자동 업데이트되었을 것으로 추정
3942
- vsix 파일로 수동 설치 시도
43+
4044
5. 동료에게 문의
45+
4146
- 민호님께 사용 중인 플러그인 버전 확인
4247
- 민호님은 v3.2.0을 마켓플레이스에서 설치해서 정상 동작 중
4348

@@ -95,4 +100,4 @@ Biome처럼 로컬 바이너리와 연동되는 플러그인의 경우, 플러
95100

96101
포맷팅이 안 된다고 바로 포기하지 말고, 플러그인의 Output 로그를 확인하는 습관이 중요해요. 이
97102
경우처럼 에러 메시지가 명확히 원인을 알려주는 경우가 많아요. VS Code/Cursor에서 View →
98-
Output을 열고 해당 플러그인을 선택하면 상세한 로그를 볼 수 있어요.
103+
Output을 열고 해당 플러그인을 선택하면 상세한 로그를 볼 수 있어요.

fundamentals/debug/pages/contribute/ios/ios_webview_image_upload_refresh_debug.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ iOS 웹뷰 라이프사이클 이해의 중요성iOS 웹뷰의 라이프사이
4848

4949
잠재의식의 힘 활용하기때로는 문제에서 잠시 벗어나 휴식을 취하는 것이 해결책을 찾는 데 도움이
5050
될 수 있습니다. 이 사례처럼 잠재의식이 문제 해결에 기여할 수도 있으니, 막힐 때는 잠시
51-
쉬어가는 것도 좋은 디버깅 전략이 될 수 있을 것 같아요.
51+
쉬어가는 것도 좋은 디버깅 전략이 될 수 있을 것 같아요.

fundamentals/debug/pages/contribute/ios/ios_webview_swipe_back_gray_screen_debug.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<br/>
44
<ContributorHeader name="김민수" githubUrl="https://github.com/minsoo-web" avatar="https://ca.slack-edge.com/E01JAGTHP8R-U077AFXLDU0-d52a2afee622-512" />
55

6-
76
## 진단하기
87

98
Vue.js로 개발한 하이브리드 앱에서 iOS의 스와이프 뒤로가기 제스처를 사용할 때 이전 화면이
@@ -21,7 +20,7 @@ Vue.js로 개발한 하이브리드 앱에서 iOS의 스와이프 뒤로가기
2120
2. 페이지에서 스크롤 동작 수행
2221
3. iOS의 스와이프 뒤로가기 제스처 사용
2322

24-
`Vue Router```scrollBehavior`` 옵션과 관련이 있을 것으로 추정하고, 공식 GitHub 저장소의 이슈를
23+
`Vue Router``scrollBehavior` 옵션과 관련이 있을 것으로 추정하고, 공식 GitHub 저장소의 이슈를
2524
검색했습니다. 비슷한 현상을 보고한 이슈에서 Evan You의 답변을 확인할 수 있었습니다. 그는
2625
이것이 Vue의 문제가 아니라 WKWebView 환경에서 swipe navigation 시 발생할 수 있는 환경적
2726
문제라고 설명했습니다.
@@ -49,9 +48,9 @@ Vue.js로 개발한 하이브리드 앱에서 iOS의 스와이프 뒤로가기
4948
최종 해결 방법은 다음과 같았습니다
5049

5150
- 페이지를 떠나기 전: `Vue Router`의 beforeEach 메서드로 현재 페이지의 이름과 스크롤 위치를
52-
`Vuex store`에 저장
51+
`Vuex store`에 저장
5352
- 페이지에 진입하기 전: `store`에 저장된 `stack`의 길이와 브라우저 `history`의 길이를 비교하여
54-
뒤로가기인지 새로운 페이지 진입인지 판단
53+
뒤로가기인지 새로운 페이지 진입인지 판단
5554
- 스크롤 위치 복원: 뒤로가기로 판단되면 저장된 스크롤 위치로 복원
5655

5756
이 방식으로 `Vue Router``scrollBehavior`에 의존하지 않고도 네이티브와 같은 스크롤 유지 경험을
@@ -73,4 +72,4 @@ Vue.js로 개발한 하이브리드 앱에서 iOS의 스와이프 뒤로가기
7372

7473
라이브러리 의존성 최소화특정 라이브러리의 기능이 환경 제약으로 동작하지 않을 때, 해당 기능을
7574
직접 구현하는 것도 좋은 해결책이에요. 이를 통해 라이브러리 의존성을 줄이고, 문제 상황에 더
76-
유연하게 대응할 수 있어요.
75+
유연하게 대응할 수 있어요.

fundamentals/debug/pages/contribute/ios/tradingview_iframe_memory_leak_debug.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<br/>
44
<ContributorHeader name="서상희" githubUrl="https://github.com/tbvjaos510" avatar="https://ca.slack-edge.com/E01JAGTHP8R-U077KGSAD4N-768549fffdd5-512" />
55

6-
76
## 진단하기
87

98
iOS Safari에서만 발생하는 메모리 누수 문제가 발견되었어요. TradingView 차트를 사용하는
@@ -60,9 +59,11 @@ TradingView 내부에서 이렇게 `AbortController`를 사용한 후, abort()
6059
문제를 해결하기 위해 단계적으로 접근했어요
6160

6261
### 1단계
62+
6363
임시 해결책 (긴급 대응)문제가 되는 코드는 TradingView의 내부 코드였기 때문에 직접 수정할 수 없었어요. 급한 대로 `AbortController`의 폴리필을 주입하여 Safari의 버그를 우회하는 방식으로 임시 해결했어요. 이를 통해 사용자들이 겪는 메모리 누수 문제를 빠르게 완화할 수 있었어요.
6464

6565
### 2단계
66+
6667
근본적인 해결책하지만 폴리필은 어디까지나 임시방편일 뿐이에요. 근본적인 문제를 해결하기 위해 Safari의 렌더링 엔진인 `WebKit`의 소스 코드를 직접 분석했어요.
6768
그리고 `WebKit` 저장소에 직접 [Pull Request](https://github.com/WebKit/WebKit/pull/50419%EB%A5%BC)를 제출하여 `AbortController`의 메모리 누수 문제를 브라우저 엔진 레벨에서 해결했어요.
6869

@@ -72,11 +73,11 @@ WebKit 구현을 바꾸지 않고 `AbortSignal`을 안전하게 사용하려면
7273

7374
```jsx
7475
const controller = new AbortController();
75-
const handler = () => console.log('aborted');
76-
controller.signal.addEventListener('abort', handler);
76+
const handler = () => console.log("aborted");
77+
controller.signal.addEventListener("abort", handler);
7778

7879
// 사용 후 반드시 다음 중 하나를 수행
79-
controller.signal.removeEventListener('abort', handler); // 이벤트 리스너 제거
80+
controller.signal.removeEventListener("abort", handler); // 이벤트 리스너 제거
8081
// 또는
8182
controller.abort(); // abort 명시적 호출
8283
```
@@ -103,4 +104,4 @@ Chrome에서 잘 작동한다고 해서 Safari에서도 동일하게 동작한
103104
오픈소스 기여를 통한 근본 해결문제의 근본 원인이 브라우저 엔진이나 오픈소스 라이브러리에
104105
있다면, 폴리필이나 workaround로 임시 해결하는 것에 그치지 말고 해당 프로젝트에 직접 기여하는
105106
것을 고려해야 해요. 이를 통해 같은 문제를 겪는 전 세계 개발자들에게 도움을 줄 수 있고,
106-
장기적으로는 더 안정적인 생태계를 만들 수 있어요.
107+
장기적으로는 더 안정적인 생태계를 만들 수 있어요.

fundamentals/debug/pages/contribute/javascript/javascript_max_number_error_debug.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ debugger; // 여기서 값이 바뀜을 확인!
3232

3333
const result = await receiveBonusChanceByPushId({ pushId });
3434
```
35+
3536
디버거를 통해 Number() 함수를 호출하는 순간 값이 변경된다는 것을 명확히 확인했어요.
3637

38+
### 원인파악
3739

38-
### 원인파악
3940
JavaScript의 Number 타입은 IEEE 754 배정밀도(64-bit) 부동소수점 형식을 사용해요. 이 형식에서
4041
정수의 안전 정밀 범위는 ≤ 2^53 - 1 = 9,007,199,254,740,991이에요.
4142

@@ -44,16 +45,17 @@ pushId 값인 17593310758458777은 이 범위를 초과하므로, Number(pushId)
4445

4546
```jsx
4647
// JavaScript의 안전한 정수 범위
47-
Number.MAX_SAFE_INTEGER // 9007199254740991
48+
Number.MAX_SAFE_INTEGER; // 9007199254740991
4849

4950
// 문제가 되는 pushId
50-
17593310758458777 > Number.MAX_SAFE_INTEGER // true
51+
17593310758458777 > Number.MAX_SAFE_INTEGER; // true
5152

5253
// 변환 시 정밀도 손실
53-
Number("17593310758458777") // 17593310758458776
54+
Number("17593310758458777"); // 17593310758458776
5455
```
5556

5657
## 수정하기
58+
5759
API에 전달할 때 string 값을 그대로 사용하도록 수정했어요. Number로 변환하지 않고 문자열
5860
그대로 서버에 보내면 정밀도 손실 없이 정확한 값을 전달할 수 있어요.
5961

@@ -65,7 +67,6 @@ await receiveBonusChanceByPushId({ pushId });
6567
// 수정 후
6668
const pushId = queryParam; // string 그대로 사용
6769
await receiveBonusChanceByPushId({ pushId });
68-
6970
```
7071

7172
## 재발방지하기
@@ -88,4 +89,4 @@ JavaScript에서 2^53 - 1보다 큰 정수를 다룰 때는 Number 타입으로
8889
### Query Parameter 처리 시 주의
8990

9091
URL query parameter는 항상 문자열이에요. 이를 다른 타입으로 변환할 때는 신중해야 해요. 특히
91-
ID 값처럼 서버에서 생성된 고유 식별자는 원본 형식을 유지하는 것이 가장 안전해요
92+
ID 값처럼 서버에서 생성된 고유 식별자는 원본 형식을 유지하는 것이 가장 안전해요

fundamentals/debug/pages/contribute/public/tossid_og_image_other_profile_debug.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,21 @@ OG 이미지를 그리는 로직이 `Selenium`으로 서버에서 브라우저
5656
```jsx
5757
// 수정 전: 동시에 여러 요청이 같은 브라우저 인스턴스 사용
5858
async function generateOG(params) {
59-
const browser = await getBrowser(); // 공유 리소스
60-
const image = await captureScreen(browser, params);
61-
return image;
59+
const browser = await getBrowser(); // 공유 리소스
60+
const image = await captureScreen(browser, params);
61+
return image;
6262
}
6363

6464
// 수정 후: Semaphore로 순차 처리 보장
6565
const ogSemaphore = new Semaphore(1); // 한 번에 하나의 요청만 처리
6666

6767
async function generateOG(params) {
68-
return await ogSemaphore.run(async () => {
69-
const browser = await getBrowser();
70-
const image = await captureScreen(browser, params);
71-
return image;
72-
});
68+
return await ogSemaphore.run(async () => {
69+
const browser = await getBrowser();
70+
const image = await captureScreen(browser, params);
71+
return image;
72+
});
7373
}
7474
```
7575

76-
이렇게 하면 OG 생성 요청들이 순차적으로 처리되어, 서로 다른 요청이 같은 브라우저 인스턴스를 간섭하지 않게 돼요.
76+
이렇게 하면 OG 생성 요청들이 순차적으로 처리되어, 서로 다른 요청이 같은 브라우저 인스턴스를 간섭하지 않게 돼요.

0 commit comments

Comments
 (0)