<?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=Gus+Hogg-Blake</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=Gus+Hogg-Blake"/>
	<link rel="alternate" type="text/html" href="https://paste.voklen.com/wiki/Special:Contributions/Gus_Hogg-Blake"/>
	<updated>2026-04-08T16:40:11Z</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=10</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=10"/>
		<updated>2026-01-04T14:49:09Z</updated>

		<summary type="html">&lt;p&gt;Gus Hogg-Blake: Fix typo&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 or 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>Gus Hogg-Blake</name></author>
	</entry>
	<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=8</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=8"/>
		<updated>2026-01-04T12:17:39Z</updated>

		<summary type="html">&lt;p&gt;Gus Hogg-Blake: Create example of file read/write queueing in Node.js&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. `writeFile` (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. `writeFile` 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 `read()` again, it just gets the existing promise.&lt;br /&gt;
&lt;br /&gt;
This queueing pattern is illustrated in the following example (untested):&lt;br /&gt;
&lt;br /&gt;
```js&lt;br /&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;
&lt;br /&gt;
async read(path) {&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
&lt;br /&gt;
reads can share a task, as there&#039;s no point reading the same data&lt;br /&gt;
&lt;br /&gt;
twice&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt;/&lt;br /&gt;
&lt;br /&gt;
let existingTask = queues[path]?.find(task =&amp;gt; task.type === &amp;quot;read&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (existingTask) {&lt;br /&gt;
&lt;br /&gt;
return existingTask.promise;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
let task = {&lt;br /&gt;
&lt;br /&gt;
type: &amp;quot;read&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
promise: promiseWithMethods(),&lt;br /&gt;
&lt;br /&gt;
inProgress: false,&lt;br /&gt;
&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
api.getQueue(path).push(task);&lt;br /&gt;
&lt;br /&gt;
api.checkQueue(path);&lt;br /&gt;
&lt;br /&gt;
return task.promise;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async write(path, data) {&lt;br /&gt;
&lt;br /&gt;
let task = {&lt;br /&gt;
&lt;br /&gt;
type: &amp;quot;write&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
data,&lt;br /&gt;
&lt;br /&gt;
promise: promiseWithMethods(),&lt;br /&gt;
&lt;br /&gt;
inProgress: false,&lt;br /&gt;
&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
api.getQueue(path).push(task);&lt;br /&gt;
&lt;br /&gt;
api.checkQueue(path);&lt;br /&gt;
&lt;br /&gt;
return task.promise;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async checkQueue(path) {&lt;br /&gt;
&lt;br /&gt;
let queue = queues[path];&lt;br /&gt;
&lt;br /&gt;
let task = queue[0];&lt;br /&gt;
&lt;br /&gt;
if (task.inProgress) {&lt;br /&gt;
&lt;br /&gt;
// if there is a task in progress, we&#039;ll recur once it&#039;s done&lt;br /&gt;
&lt;br /&gt;
// to process the next task&lt;br /&gt;
&lt;br /&gt;
return;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// we have a task waiting and it hasn&#039;t been started yet; start it&lt;br /&gt;
&lt;br /&gt;
task.inProgress = true;&lt;br /&gt;
&lt;br /&gt;
try {&lt;br /&gt;
&lt;br /&gt;
if (task.type === &amp;quot;read&amp;quot;) {&lt;br /&gt;
&lt;br /&gt;
task.promise.resolve(await api._read(path));&lt;br /&gt;
&lt;br /&gt;
} else {&lt;br /&gt;
&lt;br /&gt;
task.promise.resolve(await api._write(path, task.data));&lt;br /&gt;
&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;
&lt;br /&gt;
} catch (e) {&lt;br /&gt;
&lt;br /&gt;
task.promise.reject(e);&lt;br /&gt;
&lt;br /&gt;
} finally {&lt;br /&gt;
&lt;br /&gt;
queue.shift();&lt;br /&gt;
&lt;br /&gt;
if (queue.length &amp;gt; 0) {&lt;br /&gt;
&lt;br /&gt;
api.checkQueue(path);&lt;br /&gt;
&lt;br /&gt;
} else {&lt;br /&gt;
&lt;br /&gt;
delete queues[path];&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async _read(path) {&lt;br /&gt;
&lt;br /&gt;
return (await fs.readFile(path)).toString();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async _write(path, data) {&lt;br /&gt;
&lt;br /&gt;
return await fs.writeFile(path, data);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
}&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;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt;/&lt;br /&gt;
&lt;br /&gt;
function promiseWithMethods() {&lt;br /&gt;
&lt;br /&gt;
let resolve;&lt;br /&gt;
&lt;br /&gt;
let reject;&lt;br /&gt;
&lt;br /&gt;
let promise = new Promise(function(res, rej) {&lt;br /&gt;
&lt;br /&gt;
resolve = res;&lt;br /&gt;
&lt;br /&gt;
reject = rej;&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
promise.resolve = resolve;&lt;br /&gt;
&lt;br /&gt;
promise.reject = reject;&lt;br /&gt;
&lt;br /&gt;
return promise;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
One subtlety that has to be considered when writing the logic above is the order in which promise callbacks are called, or `awaits` 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 `await` of a promise is executed before any `await`s or `then()`s that are added subsequently. This means that from the perspective of code outside of `checkQueue`, 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>Gus Hogg-Blake</name></author>
	</entry>
	<entry>
		<id>https://paste.voklen.com/w/index.php?title=Binary_search&amp;diff=7</id>
		<title>Binary search</title>
		<link rel="alternate" type="text/html" href="https://paste.voklen.com/w/index.php?title=Binary_search&amp;diff=7"/>
		<updated>2026-01-04T11:49:46Z</updated>

		<summary type="html">&lt;p&gt;Gus Hogg-Blake: /* Purpose */ re-use word &amp;quot;iterations&amp;quot; for clarity.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Binary search is a search algorithm to find a value in a sorted array. It works by starting at the middle of the array and checking if the target value is higher or lower than the middle value. If it is lower, the algorithm then treats the bottom half as the new array and repeats the process of checking the middle value until the target value is found. If the target is higher than the midpoint, the top half of the array is used.&lt;br /&gt;
&lt;br /&gt;
Example in Python:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def binary_search(array, target):&lt;br /&gt;
    min = 0&lt;br /&gt;
    max = len(array)&lt;br /&gt;
    while min &amp;lt;= max:&lt;br /&gt;
        mid = (min + max) // 2&lt;br /&gt;
        if array[mid] &amp;lt; target:&lt;br /&gt;
            min = mid + 1&lt;br /&gt;
        elif array[mid] &amp;gt; target:&lt;br /&gt;
            max = mid - 1&lt;br /&gt;
        else:&lt;br /&gt;
            return mid&lt;br /&gt;
    # Not found&lt;br /&gt;
    return -1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Purpose ===&lt;br /&gt;
The advantage of using binary search over a simpler &amp;quot;check every element&amp;quot; approach is speed. Since each step of the algorithm approximately halves the number of elements that need to be searched, each doubling of the size of the array only adds one iteration to the &amp;quot;while&amp;quot; loop above, as opposed to doubling the number of iterations. In other words, the number of steps required grows in proportion with the base-2 logarithm of the size of the array. In algorithmic complexity notation, binary search has O(log n) time complexity.&lt;/div&gt;</summary>
		<author><name>Gus Hogg-Blake</name></author>
	</entry>
	<entry>
		<id>https://paste.voklen.com/w/index.php?title=Binary_search&amp;diff=6</id>
		<title>Binary search</title>
		<link rel="alternate" type="text/html" href="https://paste.voklen.com/w/index.php?title=Binary_search&amp;diff=6"/>
		<updated>2026-01-04T11:49:08Z</updated>

		<summary type="html">&lt;p&gt;Gus Hogg-Blake: Add section &amp;quot;Purpose&amp;quot;, focusing on performance. Explain time complexity with concrete example (doubling of input requires one more step); logarithmic growth, and big-O notation.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Binary search is a search algorithm to find a value in a sorted array. It works by starting at the middle of the array and checking if the target value is higher or lower than the middle value. If it is lower, the algorithm then treats the bottom half as the new array and repeats the process of checking the middle value until the target value is found. If the target is higher than the midpoint, the top half of the array is used.&lt;br /&gt;
&lt;br /&gt;
Example in Python:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python3&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
def binary_search(array, target):&lt;br /&gt;
    min = 0&lt;br /&gt;
    max = len(array)&lt;br /&gt;
    while min &amp;lt;= max:&lt;br /&gt;
        mid = (min + max) // 2&lt;br /&gt;
        if array[mid] &amp;lt; target:&lt;br /&gt;
            min = mid + 1&lt;br /&gt;
        elif array[mid] &amp;gt; target:&lt;br /&gt;
            max = mid - 1&lt;br /&gt;
        else:&lt;br /&gt;
            return mid&lt;br /&gt;
    # Not found&lt;br /&gt;
    return -1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Purpose ===&lt;br /&gt;
The advantage of using binary search over a simpler &amp;quot;check every element&amp;quot; approach is speed. Since each step of the algorithm approximately halves the number of elements that need to be searched, each doubling of the size of the array only adds one iteration to the &amp;quot;while&amp;quot; loop above, as opposed to doubling the number of steps. In other words, the number of steps required grows in proportion with the base-2 logarithm of the size of the array. In algorithmic complexity notation, binary search has O(log n) time complexity.&lt;/div&gt;</summary>
		<author><name>Gus Hogg-Blake</name></author>
	</entry>
</feed>