I have the following code inside a class. (It's coffeescript-- and it's for a couchdb utility!-- but this is really a node.js question). I'm attempting to do things The Node Way, using Node 0.49, and that means using asynchronous calls for filesystem operations. At first, I was pulling my hair out because this.sentinel
went to zero several times during the course of processing, so I know I'm doing something wrong there. But then I hit an even weirder issue: down in load_directory, see those console.log()
calls? Watch when happens when I run this.
check_sentinel: ->
@sentinel--
if @sentinel == 0
@emit('designDirLoaded', @object)
load_file: (rootdir, filename, object) ->
@sentinel++
fname = path.join(rootdir, filename)
@manifest.push(fname)
fs.readFile fname, (err, data) =>
object[filename] = data
@check_sentinel()
load_directory: (dirpath, object) ->
@sentinel++
fs.readdir dirpath, (err, files) =>
for fname in files
console.log("X1: ", fname)
fs.stat path.join(dirpath, fname), (err, stats) =>
console.log("X2: ", fname)
if stats.isFile()
@load_file(dirpath, fname, object)
if stats.isDirectory()
object[fname] = {}
@load_directory(path.join(dirpath, fname), object[fname])
@check_sentinel()
Here's what I get:
X1: memberByName.js
X1: memberByClub.js
X2: memberByClub.js
X2: memberByClub.js
This is surreal, and it looks a lot like a race condition. "memberByName" gets passed to fs.stat()
, which in turn passes "memberByClub" to load_file()
, implying... what? That because load_file()
returned immediately, it raced around and presented the next file name in the array to the function call? Or do I have some misunderstanding about the persistence of values in a given scope?
Yes, we can have race conditions in Node.
A simple example of a race condition is a light switch. In some homes, there are multiple light switches connected to a common ceiling light. When these types of circuits are used, the switch position becomes irrelevant. If the light is on, moving either switch from its current position turns the light off.
A race condition occurs when two threads access a shared variable at the same time.
A race condition occurs when the timing or order of events affects the correctness of a piece of code. A data race occurs when one thread accesses a mutable object while another thread is writing to it.
No, what you see is expected. One thing you have to remember is that fs.stat
is asynchronous. So, the outer loop (for fname in files
) will finish looping before any of the callbacks to fs.stat
is called.
The reason why you see memberByClub.js
twice is that you are using fname
in the logging statement, but that variable is from the closure, which has changed by the time your callback to fs.stat
is called.
You can wrap the inner loop with do (fname) =>
to get the correct logging statements, but I think you need to restructure your code to achieve what you are trying to do with the whole class.
load_directory: (dirpath, object) ->
@sentinel++
fs.readdir dirpath, (err, files) =>
for fname in files
do (fname) =>
console.log("X1: ", fname)
fs.stat path.join(dirpath, fname), (err, stats) =>
console.log("X2: ", fname)
if stats.isFile()
@load_file(dirpath, fname, object)
if stats.isDirectory()
object[fname] = {}
@load_directory(path.join(dirpath, fname), object[fname])
@check_sentinel()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With