1P by GN⁺ 19시간전 | ★ favorite | 댓글 1개
  • Ghostty 팀이 GTK 애플리케이션을 완전히 다시 작성하며 GObject 타입 시스템을 적극적으로 활용함
  • 이 과정에서 Zig 언어와의 통합 및 Valgrind를 통한 메모리 이슈 확인이 중요한 역할을 함
  • GObject 시스템 채택으로 기존보다 메모리 관리커스텀 위젯 구현이 간소화됨
  • Valgrind를 활용한 결과, Ghostty의 메모리 안전성이 크게 향상됨을 경험함
  • 새로운 Ghostty GTK가 소스 빌드의 기본이 되었으며 1.2 릴리스에 포함될 예정임

서론

  • Ghostty는 macOS, Linux, FreeBSD를 지원하는 크로스플랫폼 터미널 에뮬레이터임
  • 각 플랫폼별로 네이티브 GUI 프레임워크를 사용하여 차별점을 가짐
    • macOS: Swift 및 Xcode 기반 대용량 어플리케이션
    • Linux 및 BSD: GTK 기반 애플리케이션, X11/Wayland 등 직접 통합
    • 공통 코어는 Zig로 작성되며 C ABI 호환 API를 제공함
  • 기존 구조에서 GTK 애플리케이션을 다시 작성한 이유는 원문 PR 참고 가능
  • 본 글에서는 GObject 타입 시스템과의 연동 그리고 Valgrind로 검증한 메모리 이슈에 중점적으로 다룸

GObject 타입 시스템과 Zig

  • GTK를 사용하는 경우 기본적으로 GObject 타입 시스템과 인터페이스해야 하는 구조임
  • 과거에는 GObject 시스템을 회피하며 레퍼런스 카운팅이 없는 Zig 객체와 GObject 객체의 생명주기를 직접 맞추려 했으나, 반복적으로 메모리 해제가 제대로 되지 않는 문제 발생
    • 예: Zig 측 메모리는 해제됐지만 GTK 측 메모리는 아직 살아있거나 반대 상황 반복
  • 이런 접근은 올바름 문제뿐만 아니라 GTK 고유 기능(이벤트 신호, 속성 바인딩, 액션) 사용이 어렵게 만듦
  • 구체적 예로 설정(config) 구조체의 리로드 시 연결된 모든 GUI 요소가 일관되게 업데이트되어야 했는데, 이 과정이 복잡하고 오류가 잦았음
    • 현재는 Zig Config 구조체를 감싸는 레퍼런스 카운팅된 GhosttyConfig GObject로 관리, 속성 변경 통지로 애플리케이션 전체에 자연스럽게 변화가 전파됨
  • 커스텀 GObject 위젯 생성도 쉬워져 Blueprint 등의 현대적 GTK UI 기술 사용이 가능
    • 최근에는 Blueprint 도입으로 GTK 타이틀바 탭, 애니메이션 벨 테두리 등 새로운 기능 도입이 쉬워짐

Valgrind와 GTK, Zig

  • 개발 과정 전체에서 Valgrind로 메모리 누수, 정의되지 않은 메모리 접근 등 문제를 체계적으로 검증
  • GTK 애플리케이션의 Valgrind 검사는 까다롭고, 대용량의 suppression 파일 필요 (80%는 GTK 자체, 나머지는 3rd party 라이브러리 및 GPU 드라이버)
  • 반복적 검사로 몇몇 경우에만 발생하는 복잡한 메모리 버그를 사전에 발견 가능
    • 예: GObject WeakRef를 제대로 초기화하지 않으면 대상 객체가 나중에 해제될 때 정의되지 않은 메모리 접근이 발생, Valgrind로 사전 포착
  • 실제 경험상, Zig 코드베이스 내부 이슈는 총 2건(누수 1, 정의되지 않은 접근 1) 에 불과했으며, 그마저도 3rd party C API 연동 과정에서 발생
    • Zig의 디버그용 할당자 및 Valgrind 통합 기능도 실효성 입증
  • 기타 발견된 메모리 이슈는 대부분 C API 경계, GObject 시스템의 복잡한 생명주기 관리에서 비롯
    • 결론적으로 복잡한 라이브러리의 C API를 안전하게 사용하려면 Valgrind와 같은 도구 필요
  • Zig의 메모리 안전성 보조 기능은 이론적 논의뿐 아니라 실증적 프로젝트 경험으로도 효과를 확인

결론

  • 이번이 Ghostty GUI 부분을 다섯 번째로 처음부터 다시 만든 경험
    • GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK(절차적), Linux GTK+GObject 타입 시스템 순
  • 반복적 재작성 과정에서 매번 새로운 교훈과 기술적 성장을 얻음
    • 이번 경험을 macOS 프로젝트에도 일부 적용할 계획
  • Ghostty GTK 시스템 유지보수팀의 적극적인 협업도 강조
  • 새로 다시 작성된 Ghostty GTK 애플리케이션이 이제 소스 빌드의 디폴트가 되었으며, 1.2 정식 릴리스에 적용될 예정임
Hacker News 의견
  • GTK와 직접 일해본 경험은 없지만, 지금 설명하신 내용을 들어보면 Zig로 Godot 바인딩을 만들면서 겪는 문제와 굉장히 비슷함을 느낌. Godot는 클래스, 가상 메서드, 프로퍼티, 시그널 등 OOP 개념이 엄청 많음. 그리고 모든 개념을 다루고 사용자 정의 객체와 속성을 만들 수 있게 해주는 C API를 제공함. 엔진 객체의 수명관리를 직접 해주고, 참조 카운팅되는 객체의 트리 구조도 있음. 수명 문제를 특히 Zig 관용구에 맞춰 최적 API로 묶으려고 하니 엄청 복잡함. 이런 고민을 하다가 oopz 라이브러리도 만들었음. 아직 API 상태가 이 정도이고 실제 예시는 여기에서 볼 수 있음. Ghostty 프론트엔드를 Godot 익스텐션으로도 만들어보고 싶음

    • 예전에는 직접 언어용 GTK 바인딩을 쓰면서 불편했던 기억이 있음. 98%는 멀쩡했지만, 남은 2%에서 “이 함수가 객체의 참조를 받냐 마냐가 다른 인자에 따라 달라지는” 식의 부분들이 있었고, 그래서 객체 생명주기 분석이 상당히 골치 아팠음
    • 고마움을 전함과 동시에, C# Godot 코드를 성능 좋게 짜려고 할 때 엔진 타입들과의 상호 변환이 너무 많아서 할당도 반복적으로 발생하는 부분이 힘들었던 적이 있음. 바인딩 만들면서 이런 문제도 겪었는지 궁금함
    • Zig로 Godot 바인딩을 만드는 프로젝트가 진행 중이라는 건 몰랐음. Godot와 Zig를 모두 좋아해서 기대감이 큼. 지속적으로 관심을 가질 예정임
  • 좋은 프로그래밍이란 결국 시스템이 제공하는 방식에 맞추는 것임을 잘 보여주는 예시임. OOP나 메모리 관리를 어떻게 생각하든, GTK를 쓴다면 GObject 타입 시스템과 어떤 식으로든 인터페이스를 짜야만 함. 피하고 싶어도 결국 피해갈 수 없음. 대신 우린 피하려고 했고, 그 결과 레퍼런스 계산되는 객체와 비레퍼런스 객체의 수명을 묶는 데서 엄청난 난장판이 벌어짐. Ghostty GTK 앱에서는 Zig 메모리를 해제하면 GTK 메모리는 안 해제됐거나 반대의 버그가 반복적으로 발생함

    • GTK가 이런 구조를 갖게 된 이유가 Vala의 탄생 배경임. Vala는 C#에서 영감을 받아 GObject를 활용하고, 코드를 C로 트랜스파일함. 그래서 상당히 많은 GTK 앱들은 실제로 Vala로 작성되어 있음. 차라리 D 언어를 썼으면 더 낫지 않았을까 하는 아쉬움이 있기도 함. D는 여러 면에서 컴파일되는 C#처럼 느껴짐
    • 나쁜 시스템에 굴복하는 건 좋은 것이 아니라 실용적인 선택임
  • OOP와 메모리 관리에 대한 내 입장은 둘째치고, GTK를 쓰면 GObject 타입 시스템에 얽힐 수밖에 없다는 점에는 동의함. 그래서 아예 직접적으로 GTK를 쓰지 않기로 했음. 통일된 UI 테마가 주는 가치는 알지만, 내가 보기에 GTK의 장점들은 그만큼의 대가를 치르고 쓸 만큼 크지 않다고 느꼈음. 오픈 소스 앱에서 GTK 주변부를 만져본 경험으로, GTK와 GObject의 견해가 내 성향과 잘 맞지 않는다는 확신을 가짐. GTK가 존재하는 건 싫지 않음. 나는 안 쓰는 쪽을 골라서 괜찮은데, 일부 사람들은 그 선택이 내 권리라고 보지 않는다는 점이 이상함. 수많은 GUI 툴킷 중 하나일 뿐이고, 기술적으로 매우 정제된 툴킷임에도, GTK의 점유율이 조금만 낮았더라면 그 polish가 다른 구조적으로 더 좋은 툴킷에 쓰일 수도 있지 않았을까 싶음. 물론 내가 좋다고 생각하는 게 모두에게 좋은 건 아님. GTK를 쓰는 사람들 중 몇이나 마지못해 쓰고, 몇이나 최고의 툴이다라고 느끼는지 궁금함

    • GTK와 GObject의 의견화(Stronly Opinionated) 스타일이 내 생각과도 잘 안 맞는다는 부분에 내가 동의함을 밝힘. 나는 Gnome 생태계의 방향성과도 많이 안 맞다고 느낌. Ghostty에서 Linux용으로 GTK를 쓰는 건 상당히 실용적인 선택임. Ghostty의 목표가 플랫폼 네이티브(특히 리눅스에서)는 무엇인지 여기에 정의함. GTK는 리눅스에서 가장 널리 쓰이고, 대부분의 앱 생태계에 가장 자연스럽게 어울리기 때문에 이런 결정을 내릴 수밖에 없음. 앞으로 libghostty가 제3자에 의해 다양한 프론트엔드가 나오길 기대함. 예시로 Wayland 네이티브 Ghostty 프론트엔드인 Wraith도 있음. 멋짐
    • GTK가 리눅스에서 널리 쓰이는 핵심 이유는 바로 “C 바인딩”이 있기 때문이라고 생각함. 그래서 거의 모든 언어용 바인딩이 기본 제공되거나 자동 생성도 쉬움. 반면, Qt는 C++와 Python에 과하게 묶여 있어서 접근성이 확 떨어짐. 개발자가 어떤 언어를 쓰든 그 있는 곳에서 맞춰주는 게 중요함. 게다가 복잡한 데스크톱 앱을 짠다고 할 때, 구식 명령형 UI 툴킷이 오히려 실용적이고, 검증된 위젯이 많으니 패턴도 익숙함. 최근 방식들은 반대로 작은 것부터 다 직접 손봐야 하고, 조금만 복잡해져도 상당히 힘들어짐
    • “GTK를 안 써도 된다고 하지만, 마치 남의 선택권이 아닌 것처럼 생각하는 사람도 만난다”라는 부분에 대해 어떤 반대 의견을 주로 만났는지 궁금함. 내 기준에선 접근성이나 비로마자(Non-Roman) 입력 등을 GTK가 꽤 잘해서, 직접 만드는 개발자들이 보통 신경 안 쓰는 영역인데 이 부분을 잘 지원하는 게 주된 경쟁력처럼 보임
  • 재미있는 사실로, Ghostty와 일부 다른 GTK 앱에서 마우스가 창 밖으로 나갔다 다시 들어오면 첫 번째 스크롤 클릭이 무시되는 현상이 있음. 2015년에 처음 보고된 아주 오래된 버그 때문임. 버그 링크. 현재까지도 고칠 계획은 없고, 유지자는 Wayland를 기다리라는 입장임

    • 실제로 이 문제는 GTK 자체가 아니라 XInput2에서 발생하는 듯함. 물론 GTK가 크로미움에서 쓰는 휴리스틱처럼 우회할 수는 있지만, 근본적으로는 상위(XInput2) 문제임
    • 해당 버그 리포트와 링크된 이슈들을 읽어보면, 여러 번 고치려는 시도는 있었으나 어쩔 수 없이 일부 휴리스틱에 기대야 했고, 붙잡고 있던 문제보다 더 심각한 부작용이 계속 생겼음을 알 수 있음. 결국 근본적으로 X11 기저에서 시작된 문제라, 근본 수정이 이뤄져야 다른 개선도 의미있게 진행될 것 같음. 하지만 X11은 현재 사실상 유지보수 모드라, 팬들이 “완벽히 동작하니 추가작업 필요 없다”고 주장하는 한 기대하기 힘들 것임. 결국 남은 방법은 Wayland 전환을 기다리는 것뿐임
  • “Valgrind로 모든 단계를 검증했다”라는 부분에서, 사실 너무 당연하지만 실제로 그걸 한 적은 한 번도 없고, 다른 개발자가 그렇게 하는 것도 별로 본 적이 없음. 보통 Valgrind는 특정 버그나 성능 저하가 나타날 때만 쓰였음. 개발 과정 내내 Valgrind (특히 Memcheck, Helgrind) 같은 도구를 능동적으로 쓰면 툴의 안정성이 엄청 좋아지고, 버그 또한 도입될 때 바로 잡을 수 있어 사후에 수백개의 커밋을 뒤지는 고생도 줄일 수 있을 것 같음

    • 나 자신은 C와 C++을 쓸 때 항상 valgrind를 주기적으로 사용해왔음. valgrind와 asan이 잡는 오류들은 대개 즉각적인 크래시로 나타나지 않고 눈에 잘 안 띄는, 하지만 간헐적으로 발생하는 골치 아픈 버그로 이어지기 때문에 원인을 찾기 굉장히 어려움. 그 중엔 보안 취약점도 섞여있음. 그리고 자잘한 메모리 누수가 조금씩 쌓이다가 나중에 진짜 큰 문제가 발생할 때, 이미 무수히 쌓인 작은 누수들 때문에 원인을 찾기가 더 힘들어짐. 그래서 능동적으로 쓰는 게 좋음
    • Valgrind (특히 memcheck)는 버그 리포트를 자세히 디버깅하기 전에 쉽게 고칠 수 있는 문제부터 먼저 잡으려고 능동적으로 써옴. 다만 가장 큰 문제는 성능 오버헤드가 커서 인터랙티브하게 실행하는 경험이 별로임. 하지만 테스트를 Valgrind로 한 번씩 돌리는 건 매우 이득이라고 생각함
    • 단, Valgrind는 엄청 느리고 비싸서 코드 수정-컴파일-테스트 반복 주기에 바로 넣기엔 힘듦. 테스팅 주기(나이트리, 자동화 등)엔 쓸 수 있지만, 잘 통합하려면 추가 작업이 필요함
  • Ghostty를 쓰면서 맥에서 nano로 여러 줄 붙여넣기가 안 되는 게 매우 불편함. 터미널이 “bracketed pasting”을 어떻게 처리하느냐에 따른 것 같은데, 이상하게 iterm2나 term에서는 이런 문제가 없음

    • Ghostty가 터미널 대체 프로그램으로서 99%는 만족스럽지만, 복붙 이슈는 정말 답답하고 매일 마주침
    • 새로운 컴퓨터에 Ghostty를 기본 터미널로 쓴 이후로, 가장 아쉬운 점은 검색 기능이 없는 것임. 보통 출력 중 특정 내용을 찾으려고 단축키를 자주 쓰는데 이게 안 됨. 실제로 이슈에서도 가장 자주 언급된 문제임
    • Ubuntu 원격 접속 시 Ghostty 내에서 nano 실행 자체가 안 됨
      $ nano
      Error opening terminal: xterm-ghostty.
      
      같은 환경에서 macOS 터미널이나 VS코드 내장 터미널에서는 잘 동작함
    • 이런 현상은 진짜 버그일 수 있으니 버그 리포팅을 추천함
    • Cmd+F처럼 명령어 검색이 없는 게 가장 치명적임
  • Rust로 Zig 대신 썼다면 메모리 오류가 막혔을지 궁금함. 대부분이 Zig/C 상호작용에서 나온 문제라 Rust도 비슷했을 것 같음. Go 개발자 입장에서 추측하는데, 막상 C와 대규모로 연동할 때 더 많은 안전성 도구를 제공하는 언어가 있는지도 궁금함

    • Rust였다면 한 문제는 막았겠지만, 나머지는 마찬가지였을 것임. 지적한 것처럼 모두 C API 경계 및 의미론이 문제였기 때문에 실제 안전성은 래퍼의 품질에 따라 달라짐. Rust는 이미 잘 검증된 래퍼 생태계가 많아서 그 부분에서 자작(Zig)의 래퍼보다 위험이 낮긴 했겠지만, 결국 크게 다르지 않음. 예시로 Rust가 잡았을 것으로 보이는 undefined memory access는 실제로 이 PR에서 해결된 부분임. 실제로는 잘못된 메모리가 첫 프레임에 복사됐지만, 어딘가에 사용되거나 보내지지 않아서 심각하지 않았음. 그래도 정확하지 않은 건 확실함
    • Rust 역시 C/GObject와 FFI 경계에서는 수동 메모리 및 수명 관리를 필요로 함. Rust borrow 체크는 외부 코드 메모리 사용을 검증하지 못함
    • 글의 요점 중 하나는 zig + valgrind 조합으로 기대보다 훨씬 적은 메모리 이슈를 만난 것이라는 점임
    • Rust로 C 바인딩 짜기는 훨씬 힘듦. 따라서 rust로 gtk 바인딩 만들기 자체도 안 될 수도 있겠음
  • Ghostty 등 GPU 기반 앱(Alacritty, WezTerm, Zed 등)을 사용하면서 더 빠르고 괜찮다고 느꼈음. 하지만 아이러니하게도 이런 앱들이 Nvidia 드라이버의 한계를 더 선명히 드러내줌. 예전엔 GPU를 거의 안 써서 몰랐는데, Regolith i3wm 등 compositor 미설치 환경이나 sway/wayland 환경 모두 화면 공유, sleep 복귀, 크래시 등에서 nvidia 드라이버가 너무 형편없었음. 여러 버전(550/560/575/580) 바꿔봐도 모두 똑같음. 예전부터 이렇게 나빴구나라는 걸 최근에야 깨달음

    • 나 역시 Wayland에서 비슷한 경험을 함. X11에서 컴포지팅 효과를 꺼두니 1050Ti와 오래된 AMD(radeon 드라이버 필요) 카드 모두 문제없이 잘 돌아감. 반면 Wayland에서는 끊기거나 크래시, 깨진 화면 등 문제가 있었음
  • GTK 타입 시스템이 코드에 영향을 주지 않으면서 큰 앱 하나를 만들 수 있었음. 다만 그 대신 클래스 상속, 확장보다는 람다만 바인딩하는 형태로 모든 컴포넌트끼리 연결함. 결과적으로 그리 지저분하진 않았지만 정통 GTK 스타일에 익숙한 개발자라면 혼란스러웠을 수도 있음

  • Ghostty에 대한 과장된 관심 자체를 이해 못 하겠음. 탭과 컨텍스트 메뉴밖에 없는 UI에, 이런 통합 작업과 리라이트까지 할 가치가 있는지 의문임. iterm2처럼 강력한 GUI 환경도 추가하려는 게 아닌가 추측함. Kitty는 OpenGL로 직접 탭을 그려서 완전 커스터마이즈도 가능하고, 복잡한 프레임워크에 통합하는 시간을 아껴 엄청 실용적인 기능(마지막 명령 결과를 페이저로 감싸서 출력 등)도 빨리 구현함. Kitty에서는 원격도 잘 지원됨

    • Ghostty의 UI는 탭뿐이 아니라, 분할(Split), “프로세스 종료됨” 배너, 닫기 확인 다이얼로그, 타이틀 변경 다이얼로그, 안전하지 않은 붙여넣기 감지, 애니메이션 알림벨, 드롭다운 터미널, 진척도 바 등 생각보다 다양함. 맥에서는 Apple Shortcuts, Spotlight 통합도 있음. 물론 이런 것 없다 해도 GUI 툴킷 없이 순수하게 구현도 가능했지만, Ghostty의 미션은 각 플랫폼 네이티브 툴킷을 써서 앱이 “진짜 네이티브”처럼 체감되게 하는 것임. 이 접근이 싫으면 Kitty처럼 텍스트 탭 쓰는 것도 좋은 선택임. 추구하는 가치와 우선순위의 차이에 따라 선택할 수 있음. 앞으로 GUI 확장도 다양하게 준비 중이고, 플랫폼별 네이티브 기능 연계(iCloud 동기화 등)도 깊이 들어갈 예정임
    • “Kovid가 더 빨리 기능 구현했다”는 주장에 대해, 이 계정이 Kovid 자신인지 의심되는 이력이 있으니 조심이 필요함. 실제로 HN, Reddit에서 Kitty를 중립적으로 소개하는 척하면서 개발자를 비판하는 모습을 본 적 있음. 예전 댓글 내역까지 참고해보라고 링크 제공함
    • 위의 긍정적 설명에 더해, ‘libghostty’가 등장한 것이 게임체인저라고 생각함. WebKit처럼 누구나 drop-in만 하면 곧장 동작하는 강력한 터미널 구현체임
    • 나 역시 터미널을 찾느라 여기저기 방황해봤는데, Ghostty가 완벽히 이상적인 건 아니지만 그나마 적당히 만족스러운 걸 찾지 못했을 때 옮기게 됨. 이 역시 내겐 충분히 의미 있는 선택임. 대체로 ‘결정적 이유’ 때문이 아니라, 큰 문제가 없어서 계속 쓴다는 것이 오히려 장점임
    • 폰트가 Kitty보다 Ghostty에서 훨씬 예쁘게 렌더링됨. Neovide가 더 예쁘지만, 아직 탭 지원도 없고 배터리도 더 많이 먹음