{"id":559,"date":"2021-02-14T23:35:32","date_gmt":"2021-02-14T21:35:32","guid":{"rendered":"https:\/\/magdy.dev\/blog\/?p=559"},"modified":"2021-04-30T10:14:49","modified_gmt":"2021-04-30T08:14:49","slug":"file-locking-in-go-part-ii","status":"publish","type":"post","link":"https:\/\/magdy.dk\/blog\/2021\/02\/14\/file-locking-in-go-part-ii\/","title":{"rendered":"File Locking In Go Part II"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In the <a href=\"https:\/\/magdy.dev\/blog\/2021\/02\/07\/file-locking-in-go-part-i\/\">previous part<\/a>, we discussed locks and how to use them. In this part, we will take a more complicated example where we will see how this is used in real life.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will wrap the lock functionality in a struct to abstract all the details and ensure that the experience is consistent across our applications. This might not be the case in all applications, but we will guarantee that we have a lock and can acquire and release it.<br><br>Let&#8217;s create a new package and name it &#8220;lock&#8221; and create a file and call it &#8220;manager.go&#8221;.<br>We want to add a method to take a lock and include all the implementation details there. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func (m *Manager) TakeLock(waitToAcquire bool) error {\n   lockType := syscall.<em>LOCK_EX\n   <\/em>if !waitToAcquire {\n      lockType = lockType | syscall.<em>LOCK_NB\n   <\/em>}\n\n   file, err := os.OpenFile(m.fileName, os.<em>O_WRONLY<\/em>|os.<em>O_CREATE<\/em>, 0666)\n   if err != nil {\n      return err\n   }\n\n   m.file = file\n\n   return syscall.Flock(int(file.Fd()), lockType)\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So far, so good! We want to make sure that the package user will cleanup the package after finish using the lock, e.g., release the lock.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func (m *Manager) Close() error {\n   if err := syscall.Flock(int(m.file.Fd()), syscall.<em>LOCK_UN<\/em>); err != nil {\n      return err\n   }\n\n   return m.file.Close()\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In this way, we will release the lock and close the file when we&#8217;re done with whatever we&#8217;re doing. This is expected to be called with defer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now let&#8217;s use this in the following scenario. <br>We have two background tasks. The first task will update a directory every x minutes, and the second task will read the content and process it somehow. There can be multiple read tasks running at the same time.<br>A race condition can happen between the tasks if the first task is moving files or directories, adding or removing files, and the second background is trying to read a file that doesn&#8217;t exist anymore.<br>We will get an error message like &#8220;no such file or directory&#8221; or something similar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To protect our tasks from this case, we will use the lock just as we would have used RWMutex. We will acquire write lock when we update and read lock when we read, obvious!<br>In this case, we will ensure that the read tasks will wait if there is an update in progress, and the update task will not move stuff around if there is a read process in progress. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s write some code.<br><\/p>\n\n\n\n<div class=\"wp-block-image is-style-default\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/media.giphy.com\/media\/PiQejEf31116URju4V\/giphy.gif\" alt=\"\"\/><figcaption>Writing some code<\/figcaption><\/figure><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><br>First, let&#8217;s try the code with the race to get the feeling of it<br>We have a directory with this structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\"> \u251c\u2500\u2500 a.b\n \u251c\u2500\u2500 c.d\n \u2514\u2500\u2500 foo\n     \u2514\u2500\u2500 y.z\n 1 directory, 3 files<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func updateTask(dirName string) error {\n   dirEntries, err := ioutil.ReadDir(dirName)\n   if err != nil {\n      return fmt.Errorf(\"failed to read dir: %w\", err)\n   }\n   for _, dirEntry := range dirEntries {\n      fmt.Println(\"== \" + dirEntry.Name())\n      srcSubDirName := filepath.Join(dirName, dirEntry.Name())\n\n      if dirEntry.IsDir() {\n         destSubDirName := srcSubDirName + \"_v2\"\n         fmt.Printf(\"== Moving %s to %s\\n\", srcSubDirName, destSubDirName)\n         if err := os.Rename(srcSubDirName, destSubDirName); err != nil {\n            return err\n         }\n         continue\n      }\n\n      if dirEntry.Name() == \"c.d\" {\n         if err := os.Remove(srcSubDirName); err != nil {\n            return nil\n         }\n      }\n   }\n   return nil\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In the update task, we list files and directories of a specific path, and then we will rename it if it&#8217;s a directory and delete file &#8220;c.d.&#8221; <br>I&#8217;m trying to simulate an update script that contains some changes to the directory.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func readerTask(dirName string) error {\n   return read(dirName)\n}\n\nfunc read(dirName string) error {\n   dirEntries, err := ioutil.ReadDir(dirName)\n   if err != nil {\n      return fmt.Errorf(\"failed to read dir: %w\", err)\n   }\n\n   for _, dirEntry := range dirEntries {\n      filePath := filepath.Join(dirName, dirEntry.Name())\n      fmt.Println(\"-- Reading \" + filePath)\n\n      if dirEntry.IsDir() {\n         read(filePath)\n         continue\n      }\n\n      fileContent, err := ioutil.ReadFile(filePath)\n      if err != nil {\n         return err\n      }\n      fmt.Println(string(fileContent))\n\n      time.Sleep(1 * time.<em>Second<\/em>)\n   }\n   return nil\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In the read task, we&#8217;re doing something similar to update, but we read the files&#8217; content instead of updating them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If  we tried to run the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">go func() {\n   if err := readerTask(\"\/tmp\/test\"); err != nil {\n      fmt.Println(err.Error())\n   }\n}()\ngo func() {\n   if err := updateTask(\"\/tmp\/test\"); err != nil {\n      fmt.Println(err.Error())\n   }\n}()<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Full Code can be found at <strong><a href=\"https:\/\/gist.github.com\/ahmagdy\/484ab84c8bc6781b972867f750d30a41\/4fa10360805a8e42171b788e62ace26877973a48\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">filelockingingo.go<\/a><\/strong> <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will get the following<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash line-numbers\">== a.b\n == c.d\n -- Reading \/tmp\/test\/a.b\n == foo\n == Moving \/tmp\/test\/foo to \/tmp\/test\/foo_v2\n World\n -- Reading \/tmp\/test\/c.d\n open \/tmp\/test\/c.d: no such file or directory<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As expected, we can&#8217;t read the file content because it&#8217;s been removed by update task.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now let&#8217;s fix it by adding locks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func updateTask(dirName string) error {\n   .......\n   locker := lock.NewExclusiveLock(dirName)\n   if err := locker.TakeLock(true \/* waitToAcquire *\/); err != nil {\n      return err\n   }\n   defer locker.Close()\n   .......\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"go\" class=\"language-go\">func readerTask(dirName string) error {\n   .......\n   locker := lock.NewSharedLock(dirName)\n   if err := locker.TakeLock(true \/* waitToAcquire *\/); err != nil {\n      return err\n   }\n   defer locker.Close()\n   .......\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And try to run it again<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">== a.b\n == c.d\n == foo\n == Moving \/tmp\/test\/foo to \/tmp\/test\/foo_v2\n -- Reading \/tmp\/test\/a.b\n World\n -- Reading \/tmp\/test\/foo_v2\n -- Reading \/tmp\/test\/foo_v2\/y.z\n Hello<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It works and without any errors<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/media.giphy.com\/media\/bznNJlqAi4pBC\/giphy.gif\" alt=\"\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">File Locking is an important mechanism to ensure consistency between reading and wiring. <br>We went through a case where there is a race between reading and writing and demonstrated how we could protect the shared resources using exclusive and shared locks. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Full Code can be found <a href=\"https:\/\/gist.github.com\/ahmagdy\/484ab84c8bc6781b972867f750d30a41\/bac080d556a4426305efd4bbc0bb2df96735d266\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">here<\/a> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous part, we discussed locks and how to use them. In this part, we will take a more complicated example where we will see how this is used in real life. We will wrap the lock functionality in a struct to abstract all the details and ensure that the experience is consistent across [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[24,6],"tags":[],"class_list":["post-559","post","type-post","status-publish","format-standard","hentry","category-go","category-technology"],"aioseo_notices":[],"views":1047,"_links":{"self":[{"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/posts\/559","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/comments?post=559"}],"version-history":[{"count":18,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/posts\/559\/revisions"}],"predecessor-version":[{"id":591,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/posts\/559\/revisions\/591"}],"wp:attachment":[{"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/media?parent=559"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/categories?post=559"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/magdy.dk\/blog\/wp-json\/wp\/v2\/tags?post=559"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}