html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Golang daemon из песочницы

Около года назад мне понадобилось написать linux демона, реализующего небольшой сетевой сервис. В то время я активно изучал Go и мне очень нравился этот язык, поэтому взвесив все за и против я решил реализовать задачу на нем. К тому же, Go уже был стабильным и имел версию 1.0.1.

О том, с какими подводными камнями мне пришлось столкнуться, читайте под катом, но сразу оговорюсь: я буду описывать только тонкости реализации демона на Go. Если вы слабо представляете что такое «демон» или как демонизируется процесс, сначала стоит об этом почитать, поискав в гугле или на хабре «linux daemon» или пройдясь по списку ссылок в конце статьи.

Но вернемся к демонам. Сначала я решил действовать классически:
  • Порождение дочернего процесса и завершение родительского (системный вызов fork);
  • Далее в дочернем процессе:
    • Установка маски для прав доступа на вновь создаваемые файлы (системный вызов umask);
    • Создание нового сеанса, отключение от терминала (системный вызов setsid);
    • Смена рабочей директории на корневую (системный вызов chdir);
    • Перенаправление дескрипторов потоков стандартного ввода/вывода на /dev/null.

Отсутствие в стандартном пакете syscall чистого fork меня не остановило и даже не вызвало никаких подозрений. Я просто сделал примерно так (упрощено):
ret, _, err := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
if err != 0 {
	os.Exit(2)
}
if ret > 0 {
	// родительский процесс
	os.Exit(0)
}

Реализовав все пункты, запустив демона и полюбовавшись выводом команд ps -eafw и lsof -p <pid>, я подумал, что пора бы переходить к реализации обработки системных сигналов.

Добавление обработки сигналов поначалу мне казалось пустяковой вещью, ведь в Go есть стандартный пакет os/signal. Но когда я проделал это работу, мой демон наотрез отказывался получать эти самые сигналы. Причем если я убирал fork, обработка сигналов работала отлично. Сей факт меня весьма огорчил. Тогда я начал искать информацию в сети и, почитав code.google.com/p/go/issues/detail?id=227, огорчился еще больше. Собственно вывод был прост: В Go нельзя использовать fork, т.к. дочерний процесс не наследует потоки, а это означает, что все горутины (goroutines), заблокированные системными вызовами в потоках, отличных от текущего, отваливаются.

Тогда я оставил в покое обработку сигналов и начал экспериментировать с горутинами. Оказалось, что после вызова fork они прекрасно запускаются и работают в дочернем процессе. Открыв и почитав исходный код пакета os/signal, я понял, что все дело в этом коде:
func init() {
	signal_enable(0) // first call - initialize
	go loop()
}

Здесь, в функции инициализации пакета, функция loop() запускается в качестве отдельной горутины. Это происходит еще до вызова функции main(). Функция loop() в цикле запрашивает очередной системный вызов и передает его назначенным обработчикам. Получается, что при вызове fork, перестает функционировать loop(). Но, горутины прекрасно запускаются и работаю после вызова fork. Значит надо делать вызов этой функции init() после вызова fork, решил я.

Я полностью скопировал код пакета os/signal, элементарно переименовал функцию init() в Init() и добавил ее вызов после fork. После чего обработка сигналов заработала ценой отказа от стандартной библиотеки и путем создания велосипеда.

Спустя какое-то время я пришел к выводу, что мой демон состоит из: костыль — одна штука и велосипед — одна штука. А костыль от того, что если еще какому-то пакету захочется создать горутину в функции инициализации, то пакет откажется корректно работать в демоне. Поэтому я решил поискать немного другой путь, и копание в стандартной библиотеке натолкнуло меня на мысль использовать функцию StartProcess. Поковыряв исходники, я понял, что эта функция последовательно делает системные вызовы fork и exec безопасным образом. По сути мы ничего не теряем, только дочерний процесс как бы перезапускается заново, а значит, надо как-то сообщать ему об этом. Чтобы он мог спокойно закончить демонизацию, проведя системные вызовы далее по списку. Сначала я использовал передачу аргументов командной строки, а потом решил для уведомления дочернего процесса передавать переменную окружения _GO_DAEMON=1.

В результате я написал примерно такой код:
const (
	envVarName  = "_GO_DAEMON"
	envVarValue = "1"
)
func Reborn(umask uint32, workDir string) (err error) {
	if !WasReborn() {
		var path string
		if path, err = filepath.Abs(os.Args[0]); err != nil {
			return
		}
		cmd := exec.Command(path, os.Args[1:]...)
		envVar := fmt.Sprintf("%s=%s", envVarName, envVarValue)
		cmd.Env = append(os.Environ(), envVar)
		if err = cmd.Start(); err != nil {
			return
		}
		os.Exit(0)
	}
	syscall.Umask(int(umask))
	if len(workDir) == 0 {
		if err = os.Chdir(workDir); err != nil {
			return
		}
	}
	_, err = syscall.Setsid()
	return
}
func WasReborn() bool {
	return os.Getenv(envVarName) == envVarValue
}

Приведенный код прекрасно работает со стандартной библиотекой. Правда здесь используется пакет os/exec — высокоуровневая обертка над StartProcess.

Надо четко понимать, что здесь, в отличии от классического метода демонизации, весь ваш код, выполненный до вызова Reborn(), также будет выполнен в дочернем процессе. Если вы не хотите этого — следует использовать функцию WasReborn(). А так же дочерний процесс не наследует дескрипторы файлов (возможно, я добавлю это позже), поэтому родительский процесс должен закрывать все файлы до вызова Reborn(), а дочерний должен после вызова Reborn() перенаправлять стандартные потоки вывода в лог (также это позволит узнать что же произошло при неожиданном panic()), а ввода — на /dev/null.

После того, как мне пришлось написать еще пару демонов, я решил оформить функции демонизации в виде пакета и выложить на github: go-daemon. Так же в пакете доступны функции создания и блокировки pid-файлов и перенаправления потоков. Там же находится пример реализации простейшего демона на Go. Надеюсь этот материал будет кому-то полезен.

Ссылки:
Демон — Wikipedia
Пишем собственный linux демон
golang.org
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

2

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции