实现Run

实现Flock

前面提到进程的文件锁,实际上Run也用到了,可以试想下以下的场景。

用户A执行run pt-summary,由于本地已经缓存了所以会直接运行本地的脚本。同时用户B执行run -u pt-summary,加上-u或者--update参数后Run会从远端下载并运行最新的脚本。如果不加文件锁的话,用户A的行为就不可预测了,而文件锁很好得解决了这个问题。

具体使用方法如下,我们封装了以下的接口。

  1. var lockFile *os.File
  2. // Lock the file.
  3. func Flock(path string) error {
  4. return fcntlFlock(syscall.F_WRLCK, path)
  5. }
  6. // Unlock the file.
  7. func Funlock(path string) error {
  8. err := fcntlFlock(syscall.F_UNLCK)
  9. if err != nil {
  10. return err
  11. } else {
  12. return lockFile.Close()
  13. }
  14. }
  15. // Control the lock of file.
  16. func fcntlFlock(lockType int16, path ...string) error {
  17. var err error
  18. if lockType != syscall.F_UNLCK {
  19. mode := syscall.O_CREAT | syscall.O_WRONLY
  20. mask := syscall.Umask(0)
  21. lockFile, err = os.OpenFile(path[0], mode, 0666)
  22. syscall.Umask(mask)
  23. if err != nil {
  24. return err
  25. }
  26. }
  27. lock := syscall.Flock_t{
  28. Start: 0,
  29. Len: 1,
  30. Type: lockType,
  31. Whence: int16(os.SEEK_SET),
  32. }
  33. return syscall.FcntlFlock(lockFile.Fd(), syscall.F_SETLK, &lock)
  34. }

在运行脚本前就调用锁进程的方法。

  1. // Lock the script.
  2. lockPath := cacheDir + ".lock"
  3. err = flock.Flock(lockPath)
  4. if err != nil {
  5. utils.LogError("%s: %v\n", lockPath, err)
  6. os.Exit(1)
  7. }

实现HTTP请求

使用Run时它会自动从网上下载脚本,走的HTTP协议,具体实现方法如下。

  1. // Retrieve a file via HTTP GET.
  2. func Fetch(url string, path string) error {
  3. response, err := http.Get(url)
  4. if err != nil {
  5. return err
  6. }
  7. if response.StatusCode != 200 {
  8. return Errorf("%s: %s", response.Status, url)
  9. }
  10. defer response.Body.Close()
  11. body, err := ioutil.ReadAll(response.Body)
  12. if err != nil {
  13. return err
  14. }
  15. if strings.HasPrefix(url, MASTER_URL) {
  16. // When fetching run.conf, etc.
  17. return ioutil.WriteFile(path, body, 0644)
  18. } else {
  19. // When fetching scripts.
  20. return ioutil.WriteFile(path, body, 0777)
  21. }
  22. }
  23. `

Run的总体代码是很简单的,主要是通过解析run.conf下载相应的脚本并执行。