<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://paste.voklen.com/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Voklen</id>
	<title>PASTE Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://paste.voklen.com/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Voklen"/>
	<link rel="alternate" type="text/html" href="https://paste.voklen.com/wiki/Special:Contributions/Voklen"/>
	<updated>2026-04-08T16:38:38Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://paste.voklen.com/w/index.php?title=Example_of_file_locking_to_avoid_read/write_conflicts_in_asynchronous_code_(JavaScript)&amp;diff=9</id>
		<title>Example of file locking to avoid read/write conflicts in asynchronous code (JavaScript)</title>
		<link rel="alternate" type="text/html" href="https://paste.voklen.com/w/index.php?title=Example_of_file_locking_to_avoid_read/write_conflicts_in_asynchronous_code_(JavaScript)&amp;diff=9"/>
		<updated>2026-01-04T13:15:06Z</updated>

		<summary type="html">&lt;p&gt;Voklen: Add syntax highlighting and monospace individual words&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In a single-process application with asynchronous IO, file locking/queueing is necessary to prevent race conditions and corrupted data when reading and writing files.&lt;br /&gt;
&lt;br /&gt;
This is because there is no built-in locking or atomicity in Node&#039;s filesystem functions, so the following sequence of events is possible:&lt;br /&gt;
&lt;br /&gt;
1. Event A fires, triggering a file write. &amp;lt;code&amp;gt;writeFile&amp;lt;/code&amp;gt; (A) is called and Node starts writing to disk.&lt;br /&gt;
&lt;br /&gt;
2. Event B fires, triggering another writes to the same file. &amp;lt;code&amp;gt;writeFile&amp;lt;/code&amp;gt; is called again (B), before write A has returned.&lt;br /&gt;
&lt;br /&gt;
3. The file ends up corrupted, with some data from write A and some from write B.&lt;br /&gt;
&lt;br /&gt;
Corruption can also occur when a write occurs during a read:&lt;br /&gt;
&lt;br /&gt;
1. Some code starts reading a file.&lt;br /&gt;
&lt;br /&gt;
2. Another event fires and starts writing to the file while it&#039;s being read.&lt;br /&gt;
&lt;br /&gt;
3. The code that called the read receives a mixture of the file&#039;s contents with and without the data written in step 2.&lt;br /&gt;
&lt;br /&gt;
This means that both reads and writes must be queued. Simultaneous reads do not need to be queued, as reads do not affect the file&#039;s contents, and in fact in this example multiple read calls can be served with the same underlying promise. Whenever a write is performed, however, it must wait until any in-progress reads of writes have completed before starting, and reads must also wait until any in-progress writes have completed.&lt;br /&gt;
&lt;br /&gt;
Note that we do not need to implement locking in the multiprocessing sense, because JavaScript uses a single-threaded event loop model for processing. This means that updates to the queue&#039;s state are atomic, avoiding the kind of fine-grained race conditions we would have to account for in a multi-process environment.&lt;br /&gt;
&lt;br /&gt;
As a simple optimisation, as mentioned above, if there is already a read in the queue and the code calls &amp;lt;code&amp;gt;read()&amp;lt;/code&amp;gt; again, it just gets the existing promise.&lt;br /&gt;
&lt;br /&gt;
This queueing pattern is illustrated in the following example (untested):&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// One queue per file path&lt;br /&gt;
&lt;br /&gt;
let queues = {};&lt;br /&gt;
&lt;br /&gt;
let api = {&lt;br /&gt;
	async read(path) {&lt;br /&gt;
		/* Reads can share a task, as there&#039;s no point reading the same data twice */&lt;br /&gt;
		let existingTask = queues[path]?.find((task) =&amp;gt; task.type === &amp;quot;read&amp;quot;);&lt;br /&gt;
		if (existingTask) {&lt;br /&gt;
			return existingTask.promise;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		let task = {&lt;br /&gt;
			type: &amp;quot;read&amp;quot;,&lt;br /&gt;
			promise: promiseWithMethods(),&lt;br /&gt;
			inProgress: false,&lt;br /&gt;
		};&lt;br /&gt;
&lt;br /&gt;
		api.getQueue(path).push(task);&lt;br /&gt;
		api.checkQueue(path);&lt;br /&gt;
		return task.promise;&lt;br /&gt;
	},&lt;br /&gt;
&lt;br /&gt;
	async write(path, data) {&lt;br /&gt;
		let task = {&lt;br /&gt;
			type: &amp;quot;write&amp;quot;,&lt;br /&gt;
			data,&lt;br /&gt;
			promise: promiseWithMethods(),&lt;br /&gt;
			inProgress: false,&lt;br /&gt;
		};&lt;br /&gt;
		api.getQueue(path).push(task);&lt;br /&gt;
		api.checkQueue(path);&lt;br /&gt;
		return task.promise;&lt;br /&gt;
	},&lt;br /&gt;
&lt;br /&gt;
	async checkQueue(path) {&lt;br /&gt;
		let queue = queues[path];&lt;br /&gt;
		let task = queue[0];&lt;br /&gt;
		if (task.inProgress) {&lt;br /&gt;
			// If there is a task in progress, we&#039;ll recur once it&#039;s done&lt;br /&gt;
			// to process the next task&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
		// We have a task waiting and it hasn&#039;t been started yet; start it&lt;br /&gt;
		task.inProgress = true;&lt;br /&gt;
		try {&lt;br /&gt;
			if (task.type === &amp;quot;read&amp;quot;) {&lt;br /&gt;
				task.promise.resolve(await api._read(path));&lt;br /&gt;
			} else {&lt;br /&gt;
				task.promise.resolve(await api._write(path, task.data));&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			// Finally clause below is executed before any other promise&lt;br /&gt;
&lt;br /&gt;
			// Callbacks are called/awaits resumed&lt;br /&gt;
		} catch (e) {&lt;br /&gt;
			task.promise.reject(e);&lt;br /&gt;
		} finally {&lt;br /&gt;
			queue.shift();&lt;br /&gt;
&lt;br /&gt;
			if (queue.length &amp;gt; 0) {&lt;br /&gt;
				api.checkQueue(path);&lt;br /&gt;
			} else {&lt;br /&gt;
				delete queues[path];&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	},&lt;br /&gt;
&lt;br /&gt;
	async _read(path) {&lt;br /&gt;
		return (await fs.readFile(path)).toString();&lt;br /&gt;
	},&lt;br /&gt;
&lt;br /&gt;
	async _write(path, data) {&lt;br /&gt;
		return await fs.writeFile(path, data);&lt;br /&gt;
	},&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
/* util - Promise that can be resolved/rejected from outside */&lt;br /&gt;
&lt;br /&gt;
function promiseWithMethods() {&lt;br /&gt;
	let resolve;&lt;br /&gt;
	let reject;&lt;br /&gt;
&lt;br /&gt;
	let promise = new Promise(function (res, rej) {&lt;br /&gt;
		resolve = res;&lt;br /&gt;
		reject = rej;&lt;br /&gt;
	});&lt;br /&gt;
&lt;br /&gt;
	promise.resolve = resolve;&lt;br /&gt;
	promise.reject = reject;&lt;br /&gt;
	return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;One subtlety that has to be considered when writing the logic above is the order in which promise callbacks are called, or &amp;lt;code&amp;gt;awaits&amp;lt;/code&amp;gt; are resumed, when promises are settled. Fortunately, in JavaScript this works intuitively: callbacks are called in the order they&#039;re added, which means that code immediately following the original &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt; of a promise is executed before any &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;s or &amp;lt;code&amp;gt;then()&amp;lt;/code&amp;gt;s that are added subsequently. This means that from the perspective of code outside of &amp;lt;code&amp;gt;checkQueue&amp;lt;/code&amp;gt;, there is never a state where a task is complete but hasn&#039;t been removed from the queue yet.&lt;/div&gt;</summary>
		<author><name>Voklen</name></author>
	</entry>
</feed>