diff --git a/404.html b/404.html new file mode 100644 index 0000000..f9cf85a --- /dev/null +++ b/404.html @@ -0,0 +1,3 @@ + + dhruv-ahuja +
Page not found :(
\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..b506c62 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +dhruvahuja.me \ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 0000000..753a595 --- /dev/null +++ b/atom.xml @@ -0,0 +1,602 @@ + + + dhruv-ahuja + + + Zola + 2024-05-21T00:00:00+00:00 + https://dhruvahuja.me/atom.xml + + A Story of Optimizing Mongo DB Writes + 2024-05-21T00:00:00+00:00 + 2024-05-21T00:00:00+00:00 + + Unknown + + + https://dhruvahuja.me/posts/optimizing-mongo-writes/ + + <h2 id="introduction">Introduction</h2> +<p>I am developing an application for the game Path of Exile, that will predict item prices based on the item quantity and the item price history throughout the season. It is a straightforward implementation that updates the prices for items at a fixed frequency, and requires items and their categories to be pre-populated in the database.</p> +<p>I am running my MongoDB server on the Atlas Free Tier, hosted on AWS.</p> +<p>The core flow is as follows: there are several categories for whom we already have general information prepared, we first create <code>ItemCategory</code>  documents with this information for each category. Then we fetch data for all items belonging to that category. The <a href="https://poe.ninja" title="https://poe.ninja">poe.ninja</a> website caches its API responses and we’re able to quickly fetch the desired data even with relatively large responses. We initially made all these API calls in a loop, and the whole process was quite smooth as the response time is always quick. Upon getting the data and parsing each entity in the response array into Pydantic models, we then map the data in the form <code>&lt;category_name: item_data&gt;</code> where <code>item_data</code> is the list of items we fetched from the API. Do keep in mind that this flow will change as optimize the script down the line.</p> +<h2 id="pydantic-its-usage-here">Pydantic &amp; Its Usage Here  </h2> +<p>We create either <code>CurrencyItemEntity</code> or  <code>ItemEntity</code>  Pydantic model instances for each entity in API responses, based on whether it belongs to <code>Currency</code> or the other <code>Item</code> type, as items in the <code>Currency</code> category have a separate API response schema. Pydantic helps maintain data integrity and validates the response data, making it easier to deal with potentially unstructured third-party data (although the APIs in this case are very consistent). There would definitely be an additional overhead for parsing the item data into a Pydantic model instance for each entity, but being able to enforce schema for third-party data in this case, and getting consistent type hint support is well worth it. Its performance has also been vastly improved with the version 2.0 release that happened late last year.</p> +<h2 id="the-naive-approach-single-insertions">The Naive Approach: Single Insertions</h2> +<p>The code for the naive approach and the first iteration of the script is <a href="https://github.com/dhruv-ahuja/backend_burger/blob/c7337e97601e72dd80979ba9cf7ab25111283237/src/scripts/poe_initial.py" title="https://github.com/dhruv-ahuja/backend_burger/blob/c7337e97601e72dd80979ba9cf7ab25111283237/src/scripts/poe_initial.py">available here</a>. Here we are iterating over all categories, getting their response data and mapping them into the hashmap with <code>category name</code> as key, and the <code>data array</code> as value. It does not take much time to gather data for 31,000+ items, as mentioned above due to the quick API responses.</p> +<p>Calling <code>save_item_data</code>, It takes us an average of <strong>1216 seconds</strong> or <strong>20 minutes 16 seconds</strong> to parse each entity’s data, create and insert <code>Item</code> document instances and save them to the database one-by-one. I think this time is acceptable since the script meant to be run rarely, however it is practically very slow and not convenient. This makes extending the script or re-running it a chore. I am also interested in knowing how much time we can shave off from this, especially since there is a very simple optimization available. Memory usage for this approach would be high too, since we’re loading all item data entities in memory and have two objects for each entity. We will look into memory management after improving the execution time.</p> +<p>Each save call requires network round trips between the app and the database, and database processing time. These accumulate rapidly as we save a large number of documents one-by-one.</p> +<h2 id="the-good-approach-bulk-insertions">The Good Approach: Bulk Insertions</h2> +<p>The modified script using approach is <a href="https://github.com/dhruv-ahuja/backend_burger/blob/d88fecd8a44626445f56131544307abee500a98a/src/scripts/poe_initial.py" title="https://github.com/dhruv-ahuja/backend_burger/blob/d88fecd8a44626445f56131544307abee500a98a/src/scripts/poe_initial.py">available here</a>. I found using <code>insertMany</code> for bulk-inserts the most common and the most impactful approach, when I looked for improvement advice. Pushing all DB instances to an array and bulk-inserting them all at once, took us just ~10.7 seconds!  This is an incredible improvement and should be the first choice if you need to insert multiple documents.</p> +<p>The problem here is the memory usage, which peaks at roughly 350MB and only drops towards the end of the script, where we see memory being released.</p> +<p><img src="/images/mongodb_writes/poe_script_memory_usage.png" alt="Bulk-Inserts Memory Consumption" /></p> +<p>This can be verified by simply restricting the maximum length of the <code>item_data</code> array to 10,000, which would restrict the number of accessed item data records of the <code>BaseType</code> category, which has contains much more items. Making this change reduces the peak memory usage to ~285MB.</p> +<p><img src="/images/mongodb_writes/poe_script_limited_memory_usage.png" alt="Bulk-Inserts Memory Consumption, Restricted Object Count" /></p> +<p>We can make one more improvement which will reduce both the memory usage and execution time, but requires a significant code refactor.</p> +<h2 id="the-better-approach-producer-consumer-pattern">The Better Approach: Producer-Consumer Pattern</h2> +<p>The mostly overhauled script using this approach is <a href="https://github.com/dhruv-ahuja/backend_burger/blob/bb50fbac45fa38df28f48753690655fb2ee901b2/src/scripts/poe_initial.py" title="https://github.com/dhruv-ahuja/backend_burger/blob/bb50fbac45fa38df28f48753690655fb2ee901b2/src/scripts/poe_initial.py">available here</a>. We rewrote the main functions, moved API calls to their own functions, added more logging statements, handled errors and wrapped the async functions into async <code>Task</code>s. These pass data using dedicated <code>Queue</code>s and run until they get the termination signals using sentinel values.</p> +<p>Implementing Async Producer and Consumers means we now process information faster, by using different async tasks to concurrently get API data, parse that data, and save documents in bulk in the database.</p> +<p>This coordination allows us to reduce the time taken further to about 9 seconds, and all the tasks finish execution almost one after the other. This is an improvement of about 1.7 seconds over the bulk-insert implementation. We also witness a big drop in memory usage, with the peak memory usage being ~271MB, or an improvement of ~ 22.6% over the previous consumption of 350MB. These are fantastic results, in my opinion.</p> +<p><img src="/images/mongodb_writes/poe_script_async.png" alt="Optimal Approach Memory Consumption" /></p> +<h2 id="conclusion">Conclusion</h2> +<p>This was a journey where I got hands-on with some general performance improvements for database writes and also implemented the common but very effective Producer-Consumer design pattern. I am sure that there are things that I missed and certain aspects that can be handled better, I’ll be keeping an eye out for any improvements.</p> +<p>It was a great learning and experimental experience for me, and I hope that this made a good read for you. Please do not hesitate to <a href="mailto:dhruvahuja2k@gmail.com/" title="mailto:dhruvahuja2k@gmail.com/">email me</a> if you wish to discuss anything. I will be adding comments functionality to the site soon.</p> + + + + + Tag-Based Python CI/CD Pipeline + 2024-03-05T00:00:00+00:00 + 2024-03-05T00:00:00+00:00 + + Unknown + + + https://dhruvahuja.me/posts/tag-based-ci-cd-pipeline/ + + <h2 id="introduction">Introduction</h2> +<p>I recently setup a CI/CD pipeline using <a href="https://docs.github.com/en/actions/quickstart" title="https://docs.github.com/en/actions/quickstart">GitHub Actions</a>, to automate code quality management, testing and Docker image deployment for <a href="https://github.com/dhruv-ahuja/backend_burger" title="https://github.com/dhruv-ahuja/backend_burger">my Python webapp</a>. The CI workflow triggers on every commit to the <code>main</code> branch and formats, lints and tests the code. It uses a Redis <a href="https://docs.github.com/en/actions/using-containerized-services/about-service-containers" title="https://docs.github.com/en/actions/using-containerized-services/about-service-containers">service container</a> since the integration tests call the API endpoints, which use a caching layer before accessing the database. It also uses an action step to debug failures. The CD workflow runs on new tag pushes to the repository. Both workflows can also be run manually.</p> +<p>Let’s get started with the setup.</p> +<h2 id="initial-setup">Initial Setup</h2> +<p>Create a Docker Hub repository to push the images to, and generate a <code>read-write</code> scope Access Token for use with the workflows. Copy the token for use in the next step.</p> +<p>Next, setup environment secrets so that our application can access these values during the testing step. Go to the <code>Settings</code> → <code>Secrets and variables</code> → <code>Actions</code> panel in the GitHub repository and define the any repository secrets required during the workflow’s execution. Also define the Docker username and password secrets here. Use the access token generated above for <code>DOCKER_PASSWORD</code>.</p> +<p><img src="/images/repository_secrets.png" alt="Manage Repository Secrets" /></p> +<h2 id="creating-the-ci-workflow">Creating the CI Workflow</h2> +<p>Create a <code>.github/workflows</code> folder in your local codebase and a <code>ci.yml</code>  file, adding the following code at the top of the file:</p> +<pre data-lang="yaml" style="background-color:#212121;color:#eeffff;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">CI +</span><span> +</span><span style="color:#f78c6c;">on</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">push</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">branches</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">main +</span><span> +</span><span> </span><span style="color:#f07178;">workflow_dispatch</span><span style="color:#89ddff;">: {} +</span><span> +</span><span style="color:#f07178;">concurrency</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">group</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{ github.workflow }}-${{ github.ref }} +</span><span> </span><span style="color:#f07178;">cancel-in-progress</span><span style="color:#89ddff;">: </span><span style="color:#f78c6c;">true +</span></code></pre> +<p>This defines that the <code>CI</code> workflow runs only when code is pushed to the <code>main</code> branch. <code>workflow_dispatch: {}</code>  allows us to run the workflow manually from the <code>Actions</code> page. Our <code>concurrency</code> configuration ensures that the workflow’s runs are grouped together under one Git reference value and that only one run happens at a time. If a workflow is underway and another is triggered, the current run is cancelled in favour of the newer run.</p> +<p>Next, define the list of environment variables required by the application like so:</p> +<pre data-lang="yaml" style="background-color:#212121;color:#eeffff;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#f07178;">env</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">DB_URL</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.DB_URL}} +</span><span> </span><span style="color:#f07178;">JWT_SECRET_KEY</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.JWT_SECRET_KEY}} +</span><span> </span><span style="color:#f07178;">AWS_ACCESS_KEY_ID</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.AWS_ACCESS_KEY_ID}} +</span><span> </span><span style="color:#f07178;">AWS_SECRET_ACCESS_KEY</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.AWS_SECRET_ACCESS_KEY}} +</span><span> </span><span style="color:#f07178;">AWS_REGION_NAME</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.AWS_REGION_NAME}} +</span><span> </span><span style="color:#f07178;">SQS_QUEUE_NAME</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.SQS_QUEUE_NAME}} +</span><span> </span><span style="color:#f07178;">S3_BUCKET_URL</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.S3_BUCKET_URL}} +</span><span> </span><span style="color:#f07178;">S3_BUCKET_NAME</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.S3_BUCKET_NAME}} +</span><span> </span><span style="color:#f07178;">S3_LOGS_FOLDER</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.S3_LOGS_FOLDER}} +</span><span> </span><span style="color:#f07178;">REDIS_HOST</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">localhost +</span></code></pre> +<p>We define environment variables by reading the repository secrets we set in the initial setup section, with the exception of <code>REDIS_HOST</code>, which is set to <code>localhost</code> to enable our application access to the Redis service.</p> +<p>Now comes the main part for the CI logic, the job itself:</p> +<pre data-lang="yaml" style="background-color:#212121;color:#eeffff;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#f07178;">jobs</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">build</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">runs-on</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">ubuntu-latest +</span><span> +</span><span> </span><span style="color:#f07178;">services</span><span style="color:#89ddff;">: +</span><span> </span><span style="font-style:italic;color:#4a4a4a;"># Label used to access the service container +</span><span> </span><span style="color:#f07178;">redis</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">image</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">redis +</span><span> </span><span style="font-style:italic;color:#4a4a4a;"># Set health checks to wait until redis has started +</span><span> </span><span style="color:#f07178;">options</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">&gt;</span><span style="color:#c792ea;">- +</span><span style="color:#c3e88d;"> --health-cmd &quot;redis-cli ping&quot; +</span><span style="color:#c3e88d;"> --health-interval 10s +</span><span style="color:#c3e88d;"> --health-timeout 5s +</span><span style="color:#c3e88d;"> --health-retries 5 +</span><span style="color:#c3e88d;"> +</span><span> </span><span style="color:#f07178;">ports</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#c3e88d;">6379:6379 +</span><span> +</span><span> </span><span style="color:#f07178;">steps</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Checkout +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">actions/checkout@v4 +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Setup Python +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">actions/setup-python@v4 +</span><span> </span><span style="color:#f07178;">with</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">python-version</span><span style="color:#89ddff;">: &quot;</span><span style="color:#c3e88d;">3.11</span><span style="color:#89ddff;">&quot; +</span><span> </span><span style="color:#f07178;">cache</span><span style="color:#89ddff;">: &quot;</span><span style="color:#c3e88d;">pip</span><span style="color:#89ddff;">&quot; +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Install PyCurl Dependencies +</span><span> </span><span style="color:#f07178;">run</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#c3e88d;">sudo apt-get update &amp;&amp; sudo apt-get install -y curl libcurl4-openssl-dev build-essential libssl-dev +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Install Dependencies +</span><span> </span><span style="color:#f07178;">run</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#c3e88d;">python -m pip install --upgrade pip +</span><span> </span><span style="color:#c3e88d;">pip install -r requirements.txt +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Test code +</span><span> </span><span style="color:#f07178;">run</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#c3e88d;">pytest . -s -v -W ignore +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Check Code Formatting +</span><span> </span><span style="color:#f07178;">run</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#c3e88d;">ruff format --line-length=120 --check . +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Check Code Linting +</span><span> </span><span style="color:#f07178;">run</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#c3e88d;">ruff check . +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Setup Tmate Session +</span><span> </span><span style="color:#f07178;">if</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{ failure() }} +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">mxschmitt/action-tmate@v3 +</span></code></pre> +<p>Let’s walk through the job’s specifics, step-by-step.</p> +<p>The Redis service logic sets up a Redis container with health check options to ensure that the workflow waits for it to boot up, and exposes port <code>6379</code> to make it accessible to the application when we run the tests.</p> +<p><code>Checkout</code> makes the repository’s code available to the workflow, and <code>Setup Python</code> setups the specific Python version — <code>3.11</code> in our case — and caches the dependencies installed by <code>pip</code> to make future workflow runs faster. <code>Install Pycurl Dependencies</code> installs the dependencies required by the <code>pycurl</code> Python library on Ubuntu. The following step installs the Python dependencies used by our application.</p> +<p>The code testing step runs the <code>pytest</code> test suite gathers and runs all tests in the current directory. My project has a few unit tests and integration tests for each API endpoint. The <code>-s</code> flag outputs any Python print statements to the stdout stream, and <code>-v</code> runs the tests in verbose mode, giving us a detailed overview of the ongoing tests. I have added <code>-W ignore</code> to ignore the warnings emitted during the execution of the tests, primarily to help avoid the <code>Pydantic v1 deprecation warnings</code> issued for third party libraries.</p> +<p>I am using <code>Ruff</code> as the formatter and linter of choice, it is very fast and I feel that it has good linting rules without being overly restrictive. It is easy to setup formatting, lint and type checks in editors and is a one-time setup and I feel that it really helps keep the codebase maintainable in the long run.</p> +<p>The next two steps check for formatting and lint errors in the code and stop the workflow in case of any errors. This ensures that contributing developers adhere to Ruff’s code quality standards.</p> +<p>The last step is optional, and only runs if any of the previous steps fails. It allows us to ssh into the currently ongoing workflow session to check the environment and debug issues. Be careful though, it kept running for quite a while since I forgot to cancel the workflow run manually.  I am not sure if it has a time limit or it keeps running indefinitely.</p> +<h2 id="creating-the-cd-workflow">Creating the CD workflow</h2> +<p> The CD pipeline is quite straightforward:</p> +<pre data-lang="yaml" style="background-color:#212121;color:#eeffff;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">CD +</span><span> +</span><span style="color:#f78c6c;">on</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">push</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">tags</span><span style="color:#89ddff;">: &quot;</span><span style="color:#c3e88d;">v*</span><span style="color:#89ddff;">&quot; +</span><span> +</span><span> </span><span style="font-style:italic;color:#4a4a4a;"># allow manually triggering this workflow +</span><span> </span><span style="color:#f07178;">workflow_dispatch</span><span style="color:#89ddff;">: {} +</span><span> +</span><span style="color:#f07178;">concurrency</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">group</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{ github.workflow }}-${{ github.ref }} +</span><span> </span><span style="color:#f07178;">cancel-in-progress</span><span style="color:#89ddff;">: </span><span style="color:#f78c6c;">true +</span><span> +</span><span style="color:#f07178;">jobs</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">deploy</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">runs-on</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">ubuntu-latest +</span><span> +</span><span> </span><span style="color:#f07178;">steps</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Checkout +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">actions/checkout@v4 +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Log into Docker Hub +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">docker/login-action@v3 +</span><span> </span><span style="color:#f07178;">with</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">username</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.DOCKER_USERNAME}} +</span><span> </span><span style="color:#f07178;">password</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{secrets.DOCKER_PASSWORD}} +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Get Latest Tag +</span><span> </span><span style="color:#f07178;">id</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">latest-tag +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: &quot;</span><span style="color:#c3e88d;">WyriHaximus/github-action-get-previous-tag@v1</span><span style="color:#89ddff;">&quot; +</span><span> </span><span style="color:#f07178;">with</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">fallback</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">latest +</span><span> +</span><span> </span><span style="color:#89ddff;">- </span><span style="color:#f07178;">name</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Build and push Docker image +</span><span> </span><span style="color:#f07178;">uses</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">docker/build-push-action@v5 +</span><span> </span><span style="color:#f07178;">with</span><span style="color:#89ddff;">: +</span><span> </span><span style="color:#f07178;">push</span><span style="color:#89ddff;">: </span><span style="color:#f78c6c;">true +</span><span> </span><span style="color:#f07178;">tags</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">dhruvahuja/backend_burger:${{ steps.latest-tag.outputs.tag }} +</span><span> </span><span style="color:#f07178;">labels</span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">${{steps.latest-tag.outputs.tag}} +</span></code></pre> +<p>We define the workflow to either run manually or when a new tag prefixed by <code>v</code> is pushed to the repository, example. <code>v0.0.1</code>. <code>Checkout</code> is required to allow getting git tag in the third step. The next step reads the Docker username and password from repository secrets and logs us into Docker Hub. <code>Get Latest Tag</code> reads the tag which was just pushed, if the workflow was triggered by a tag push, otherwise defaulting to <code>latest</code>.</p> +<p>The final step builds the Docker image with the version tag and label, and pushes it to the Docker repository URL, defined in the <code>tags</code> directive. In this case, <code>dhruvahuja/backend_burger</code>.</p> +<h2 id="conclusion">Conclusion</h2> +<p>That’s it, our CI/CD pipeline is ready! There are Actions available for all sort of use-cases, and you can remove or add steps according to your needs. For example, you may choose to ssh into a server after the <code>Build and push Docker image</code> step to pull and run the new image. I did not add this particular step since I did not have a need for it at the moment.</p> +<p>I chose the tag-based approach for the deployment process since I wanted to deploy new images only on specific milestones, which I can manage with version tags.</p> + + + + + Writing Rust Bindings for My Python App + 2023-11-06T00:00:00+00:00 + 2023-11-06T00:00:00+00:00 + + Unknown + + + https://dhruvahuja.me/posts/writing-rust-bindings/ + + <h2 id="introduction">Introduction</h2> +<p><a href="https://github.com/dhruv-ahuja/spoti-dl" title="https://github.com/dhruv-ahuja/spoti-dl">spoti-dl</a>, a Python-based CLI song downloading tool was the first “proper” application that I developed. It acted as a proof-of-concept of my programming skills as a self-taught developer, and helped me land my first job. However, it lacked some basic features, mainly- no parallel downloads for albums and playlists.</p> +<p>I recently added a few new features and re-wrote its core functionality in Rust, as I have been enjoying working with Rust’s robust type system, compiler-level error handling and syntax.</p> +<h2 id="development">Development</h2> +<p>Development was relatively smooth for the most part, as the app logic is straightforward — you accept and parse the input Spotify link, the CLI flag parameters and process downloads. I figured out general things by googling and/or through some experimentation, such as the trait implementations to parse CLI flags from <code>String</code>s into <code>enum</code>s and vice-versa. The <code>lazy_static</code> macro helped me allocate a static <code>HashSet</code> containing disallowed characters for files and folder names, on runtime. I also became more comfortable with bound traits and experienced the power of generics. I was able to use the following function across all of my download flows, as it accepts any input <code>P</code> that can be referenced as <code>Path</code> and any input <code>S</code> that can be converted into a <code>String</code> type:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">add_metadata</span><span style="color:#89ddff;">&lt;</span><span>P, S</span><span style="color:#89ddff;">&gt;( +</span><span> </span><span style="color:#f78c6c;">file_path</span><span style="color:#89ddff;">:</span><span> P, +</span><span> </span><span style="color:#f78c6c;">album_art_path</span><span style="color:#89ddff;">:</span><span> P, +</span><span> </span><span style="color:#f78c6c;">simple_song</span><span style="color:#89ddff;">: </span><span>spotify</span><span style="color:#89ddff;">::</span><span>SimpleSong, +</span><span> </span><span style="color:#f78c6c;">album_name</span><span style="color:#89ddff;">:</span><span> S, +</span><span style="color:#89ddff;">) </span><span style="color:#c792ea;">where +</span><span> P</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">AsRef</span><span style="color:#89ddff;">&lt;</span><span>Path</span><span style="color:#89ddff;">&gt;</span><span> + Debug, +</span><span> S</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">Into</span><span style="color:#89ddff;">&lt;</span><span style="color:#ffcb6b;">String</span><span style="color:#89ddff;">&gt;</span><span>, +</span><span style="color:#89ddff;">{...} +</span></code></pre> +<p>I mainly struggled when implementing the async logic to download songs in parallel, due to my inexperience with writing async code in Rust. I had to spend a lot of time working with the compiler’s restrictions and <a href="https://tokio.rs/" title="https://tokio.rs/">Tokio’s</a> <code>’static + Send</code> requirements for spawning tasks, as its work-stealing scheduler model means that a task running in one thread could be picked up by another thread. I used <code>tokio::task::block_in_place</code> to wrap the <code>add_metadata</code> function call as the <a href="https://github.com/Serial-ATA/lofty-rs" title="https://github.com/Serial-ATA/lofty-rs">lofty</a> crate does not support async.</p> +<p>I added a CLI flag, allowing users to specify the number of tasks to use to process parallel downloads, and used batch downloads of 100 songs for playlists, as they can contain several thousands of songs.</p> +<p>The following is the core async logic for parallel downloads — calculate songs to be downloaded by each task, make <code>Arc</code>s to pass cheap, shareable clones for certain values, chunk the list of songs and create and wait for the spawned tasks to finish downloads:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">let</span><span> parallel_tasks</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">usize </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">if</span><span> album</span><span style="color:#89ddff;">.</span><span>songs</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() &gt;=</span><span> cli_args</span><span style="color:#89ddff;">.</span><span>parallel_downloads </span><span style="color:#89ddff;">as </span><span style="font-style:italic;color:#c792ea;">usize </span><span style="color:#89ddff;">{ +</span><span> cli_args</span><span style="color:#89ddff;">.</span><span>parallel_downloads </span><span style="color:#89ddff;">as </span><span style="font-style:italic;color:#c792ea;">usize +</span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">else </span><span style="color:#89ddff;">{ +</span><span> album</span><span style="color:#89ddff;">.</span><span>songs</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() +</span><span style="color:#89ddff;">}; +</span><span> +</span><span style="font-style:italic;color:#c792ea;">let</span><span> songs_per_task </span><span style="color:#89ddff;">=</span><span> album</span><span style="color:#89ddff;">.</span><span>songs</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() /</span><span> parallel_tasks</span><span style="color:#89ddff;">; +</span><span style="font-style:italic;color:#c792ea;">let</span><span> remaining_songs </span><span style="color:#89ddff;">=</span><span> album</span><span style="color:#89ddff;">.</span><span>songs</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() %</span><span> parallel_tasks</span><span style="color:#89ddff;">; +</span><span> +</span><span style="font-style:italic;color:#c792ea;">let</span><span> cli_args </span><span style="color:#89ddff;">= </span><span>Arc</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span>cli_args</span><span style="color:#89ddff;">); +</span><span style="font-style:italic;color:#c792ea;">let</span><span> album_art_dir </span><span style="color:#89ddff;">= </span><span>Arc</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span>album_art_dir</span><span style="color:#89ddff;">); +</span><span style="font-style:italic;color:#c792ea;">let</span><span> album_name </span><span style="color:#89ddff;">= </span><span>Arc</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span>album</span><span style="color:#89ddff;">.</span><span>name</span><span style="color:#89ddff;">); +</span><span> +</span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> handles </span><span style="color:#89ddff;">= </span><span style="color:#ffcb6b;">Vec</span><span style="color:#89ddff;">::</span><span>with_capacity</span><span style="color:#89ddff;">(</span><span>parallel_tasks</span><span style="color:#89ddff;">); +</span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> start </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">; +</span><span> +</span><span style="font-style:italic;color:#c792ea;">for</span><span> i </span><span style="color:#89ddff;">in </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">..</span><span>parallel_tasks </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> end </span><span style="color:#89ddff;">=</span><span> start </span><span style="color:#89ddff;">+</span><span> songs_per_task</span><span style="color:#89ddff;">; +</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> i </span><span style="color:#89ddff;">&lt;</span><span> remaining_songs </span><span style="color:#89ddff;">{ +</span><span> end </span><span style="color:#89ddff;">+= </span><span style="color:#f78c6c;">1 +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> songs_chunk </span><span style="color:#89ddff;">= &amp;</span><span>album</span><span style="color:#89ddff;">.</span><span>songs</span><span style="color:#89ddff;">[</span><span>start</span><span style="color:#89ddff;">..</span><span>end</span><span style="color:#89ddff;">]; +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> handle </span><span style="color:#89ddff;">= </span><span>tokio</span><span style="color:#89ddff;">::</span><span>spawn</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">download_songs</span><span style="color:#89ddff;">( +</span><span> file_path</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">clone</span><span style="color:#89ddff;">(), +</span><span> cli_args</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">clone</span><span style="color:#89ddff;">(), +</span><span> album_art_dir</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">clone</span><span style="color:#89ddff;">(), +</span><span> album_name</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">clone</span><span style="color:#89ddff;">(), +</span><span> songs_chunk</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">to_vec</span><span style="color:#89ddff;">(), +</span><span> </span><span style="color:#89ddff;">)); +</span><span> +</span><span> start </span><span style="color:#89ddff;">=</span><span> end</span><span style="color:#89ddff;">; +</span><span> handles</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">push</span><span style="color:#89ddff;">(</span><span>handle</span><span style="color:#89ddff;">) +</span><span style="color:#89ddff;">} +</span><span> +</span><span style="font-style:italic;color:#c792ea;">for</span><span> handle </span><span style="color:#89ddff;">in</span><span> handles </span><span style="color:#89ddff;">{ +</span><span> handle</span><span style="color:#89ddff;">.</span><span>await</span><span style="color:#89ddff;">?; +</span><span style="color:#89ddff;">} +</span></code></pre> +<h2 id="tooling">Tooling</h2> +<p>I dropped <a href="https://python-poetry.org/" title="https://python-poetry.org/">Poetry</a> as it would not be compatible with the Rust bindings and used simple virtual environments for dependency management, and <a href="https://twine.readthedocs.io/en/stable/" title="https://twine.readthedocs.io/en/stable/">Twine</a> for distributing built wheels.</p> +<p><a href="https://pyo3.rs/v0.20.0/" title="https://pyo3.rs/v0.20.0/">Pyo3</a> acts as the bridge between the parent Python code that calls a single exposed Rust function and enables all the inter-op between the two systems. <a href="https://github.com/PyO3/maturin" title="https://github.com/PyO3/maturin">Maturin</a> compiles the Rust code into a Python library, and also compiles both codebases into a distributable Python wheel.</p> +<p>The following is a list of changes I had to make in my <code>Cargo</code> and <code>pyproject</code> TOML files, to ensure that the build process and <code>pip</code> installed package worked as intended:</p> +<ul> +<li> +<p><code>Maturin</code> did not recognize the project as a mixed Python-Rust project, hence did not include Rust code in the distributable Python wheel. Setting <code>lib.name</code> table’s value to match Python source directory (<code>spotidl</code>) in <code>Cargo.toml</code> fixed this error.</p> +</li> +<li> +<p><code>pyproject.toml</code> required several modifications — I needed to set the <code>project.scripts</code> value to <code>spoti-dl = &quot;spotidl.main:main&quot;</code>, partially because the project name (<code>spoti-dl</code>) and Python source directory names were different. I also added the <code>python-packages = [&quot;spotidl&quot;]</code> value under <code>tool.maturin</code> to ensure its inclusion during the build process. I also had to add my dependencies and relevant project metadata in their apt sections, after dropping <code>Poetry</code>.</p> +</li> +<li> +<p><code>Maturin</code> compiles the Rust code as a library inside our Python source directory. It adds an underscore <code>_</code> to the library’s name by default, which is quite confusing. I rectified this by configuring the <code>module-name</code> value under <code>tool.maturin</code>.</p> +</li> +</ul> +<p>I faced several problems when attempting to build wheels for Linux using Docker, on my M1 MacBook. I must have easily spent 15-20 hours trying to get the <code>openssl-sys</code> crate to compile as it was the single point of failure, using both the python <code>manylinux</code> and <code>maturin</code> Docker images. I tried to integrate a CI/CD setup using GitHub Actions too, but to no avail, as the crate kept failing to compile. You can check the graveyard of my CI’s failed runs <a href="https://github.com/dhruv-ahuja/spoti-dl/actions" title="https://github.com/dhruv-ahuja/spoti-dl/actions">here</a>. Eventually I had to settle for manually compiling wheels on Linux, Mac and Windows and copying them to a folder before publishing them with <code>Twine</code>.</p> +<h2 id="conclusion">Conclusion</h2> +<p>This was a rewarding experience for me, as I dealt with efficiently processing large amounts of data and sharpened my skills with Rust and <code>Tokio</code>.</p> +<p>I witnessed a 20-25% speed increase and 50% less memory consumption in my Rust code when downloading a single song. The development process was smooth as <code>Pyo3</code> and <code>Maturin</code> are very well-documented and provide convenient APIs, make it incredibly easy to get started with writing FFIs for Python.</p> + + + + + Implementing a Naive Buffered Queue in Rust + 2023-09-06T00:00:00+00:00 + 2023-09-06T00:00:00+00:00 + + Unknown + + + https://dhruvahuja.me/posts/implementing-buffered-queue-in-rust/ + + <h2 id="introduction">Introduction</h2> +<p>O’Reilly’s <code>Programming Rust</code> book walks us through optimizing a part of a pipeline, in Chapter 19 <code>Concurrency</code>. It explains how a channel-based pipeline can encounter slowdowns and high memory usage if one of the consumer threads is much slower than one of the producer threads. The producer keeps adding tasks to the queue, but the consumer is unable to consume them at a satisfactory pace. The queue will have a large amount of unconsumed data causing memory spikes. Defining fixed capacities will lower memory consumption in applications without affecting the latencies since the consumer already consumes at its own fixed pace.</p> +<p>I had known about queues but had never thought about them in a larger scope, so I thought attempting a custom implementation would be a good way to learn more. I received a lot of help from the Rust community for this project, allowing me to better understand the concepts and improve my code :)  </p> +<h2 id="overview">Overview</h2> +<p>We will walk through the implementation of a simple multi-threaded, blocking, buffered queue. The Producer thread will push elements till the queue is at capacity, and block until the queue has space again. Similarly, the Consumer thread will consume elements till the queue is empty, and block until it has elements again. We do not persist the threads once the input stream is expended.</p> +<h2 id="declaring-our-types">Declaring our Types</h2> +<p>We can create a new project with <code>cargo new buffered-queue-rs</code> and put our queue logic in <code>src/lib.rs</code>, marking all code inside the file as library code. This makes it accessible to the whole project by importing it with the project name specified in the <code>cargo new</code> command.</p> +<p>Add the following imports to the file:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>collections</span><span style="color:#89ddff;">::</span><span>VecDeque</span><span style="color:#89ddff;">; +</span><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>sync</span><span style="color:#89ddff;">::</span><span>atomic</span><span style="color:#89ddff;">::{</span><span>AtomicBool</span><span style="color:#89ddff;">,</span><span> Ordering</span><span style="color:#89ddff;">}; +</span><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>sync</span><span style="color:#89ddff;">::{</span><span>Arc</span><span style="color:#89ddff;">,</span><span> Condvar</span><span style="color:#89ddff;">,</span><span> Mutex</span><span style="color:#89ddff;">,</span><span> MutexGuard</span><span style="color:#89ddff;">}; +</span></code></pre> +<p>Next, we will define the types for our buffered queue implementation:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>Producer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;(</span><span>Arc</span><span style="color:#89ddff;">&lt;</span><span>BufferedQueue</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;&gt;); +</span><span> +</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>Consumer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;(</span><span>Arc</span><span style="color:#89ddff;">&lt;</span><span>BufferedQueue</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;&gt;); +</span><span> +</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>BufferedQueue</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> data</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">&lt;</span><span>VecDeque</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;&gt;</span><span>, +</span><span> </span><span style="color:#c792ea;">pub </span><span>capacity</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">usize</span><span>, +</span><span> </span><span style="color:#c792ea;">pub </span><span>is_full</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">&lt;</span><span style="font-style:italic;color:#c792ea;">bool</span><span style="color:#89ddff;">&gt;</span><span>, +</span><span> </span><span style="color:#c792ea;">pub </span><span>is_full_signal</span><span style="color:#89ddff;">:</span><span> Condvar, +</span><span> </span><span style="color:#c792ea;">pub </span><span>is_empty</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">&lt;</span><span style="font-style:italic;color:#c792ea;">bool</span><span style="color:#89ddff;">&gt;</span><span>, +</span><span> </span><span style="color:#c792ea;">pub </span><span>is_empty_signal</span><span style="color:#89ddff;">:</span><span> Condvar, +</span><span> </span><span style="color:#c792ea;">pub </span><span>elements_processed</span><span style="color:#89ddff;">:</span><span> AtomicBool, +</span><span style="color:#89ddff;">} +</span></code></pre> +<p>These are <a href="https://doc.rust-lang.org/book/ch10-01-syntax.html" title="https://doc.rust-lang.org/book/ch10-01-syntax.html">generic</a> types, signified by the type parameter <code>&lt;T&gt;</code>, and can be used with any type as we have not defined any constraints on the type <code>T</code>.</p> +<p><code>Producer</code> and <code>Consumer</code> follow the <a href="https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html" title="https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html">NewType</a> pattern, allowing us to specify special behaviour on the wrapped type. It will help us separate producer and consumer concerns.</p> +<p>All the defined types use an <a href="https://doc.rust-lang.org/std/sync/struct.Arc.html" title="https://doc.rust-lang.org/std/sync/struct.Arc.html">Arc</a>, a special pointer type that enables cheap shared access to data. It also allows sharing its pointer values across threads, even though the wrapped value might not be shareable. It maintains a reference counter for each reference active in memory, similar to Python objects.</p> +<p>Our internal queue implementation <code>data</code> is a double-ended queue, held by a mutex to prevent data inconsistencies and enforce exclusive data access. <code>capacity</code> is the user-defined maximum capacity for our queue. <code>usize</code> data type ensures that the value cannot be negative. <code>is_full</code> and <code>is_empty</code> indicate the queue’s current state. They will be used by the <code>is_full_signal</code> and <code>is_empty_signal</code> <a href="https://doc.rust-lang.org/std/sync/struct.Condvar.html" title="https://doc.rust-lang.org/std/sync/struct.Condvar.html">Condvars</a> to allow the producer and consumer threads to wait until the queue is in their desired state. <code>elements_processed</code> is an <a href="https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html" title="https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html">AtomicBool</a> and is thread-safe.  </p> +<p>The <code>Operation</code> enum type will signal the queue’s state updates to listening threads. It maps to the queue’s push and pop operations:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">enum </span><span>Operation&lt;&#39;a&gt; </span><span style="color:#89ddff;">{ +</span><span> Push </span><span style="color:#89ddff;">{</span><span> is_full_flag</span><span style="color:#89ddff;">: </span><span>MutexGuard</span><span style="color:#89ddff;">&lt;</span><span style="color:#c792ea;">&#39;a</span><span>, </span><span style="font-style:italic;color:#c792ea;">bool</span><span style="color:#89ddff;">&gt; }, +</span><span> Pop </span><span style="color:#89ddff;">{</span><span> is_empty_flag</span><span style="color:#89ddff;">: </span><span>MutexGuard</span><span style="color:#89ddff;">&lt;</span><span style="color:#c792ea;">&#39;a</span><span>, </span><span style="font-style:italic;color:#c792ea;">bool</span><span style="color:#89ddff;">&gt; }, +</span><span style="color:#89ddff;">} +</span></code></pre> +<p>Acquiring the lock on a mutex returns a <a href="https://doc.rust-lang.org/std/sync/struct.MutexGuard.html" title="https://doc.rust-lang.org/std/sync/struct.MutexGuard.html">MutexGuard</a>, a thin wrapper around the value held by the mutex. The <a href="https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html" title="https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html">lifetime specifier</a> <code>&lt;’a&gt;</code>  in the type definition indicates how long the boolean flags are going to stay in memory. They are now associated with the enum variants and their held locks will be unlocked when the enum variants go out of scope.</p> +<p>We can see Rust’s powerful enums here, as we can add data on individual variants like we would with a struct.</p> +<h2 id="defining-producer-and-consumer-logic">Defining Producer and Consumer Logic</h2> +<p>Producer and consumer have a similar logical flow. Both have 2 methods, the <code>len</code> method is common to both types and wraps a call to <code>BufferedQueue</code>‘s <code>len</code> method.  </p> +<h3 id="producer">Producer</h3> +<p>Producer’s implementation is:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">impl</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; </span><span>Producer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> </span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">push</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span>, </span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">:</span><span> T</span><span style="color:#89ddff;">) { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> queue_is_full </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>is_full</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="font-style:italic;color:#c792ea;">while </span><span style="color:#89ddff;">*</span><span>queue_is_full </span><span style="color:#89ddff;">{ +</span><span> queue_is_full </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>is_full_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">wait</span><span style="color:#89ddff;">(</span><span>queue_is_full</span><span style="color:#89ddff;">).</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> queue </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>data</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> queue</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">push_back</span><span style="color:#89ddff;">(</span><span>value</span><span style="color:#89ddff;">); +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">pushed element</span><span style="color:#89ddff;">&quot;); +</span><span> +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span style="color:#82aaff;">signal_queue_changes</span><span style="color:#89ddff;">( +</span><span> queue</span><span style="color:#89ddff;">, +</span><span> Operation</span><span style="color:#89ddff;">::</span><span>Push </span><span style="color:#89ddff;">{ +</span><span> is_full_flag</span><span style="color:#89ddff;">:</span><span> queue_is_full</span><span style="color:#89ddff;">, +</span><span> </span><span style="color:#89ddff;">}, +</span><span> </span><span style="color:#89ddff;">); +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">) -&gt; </span><span style="font-style:italic;color:#c792ea;">usize </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() +</span><span> </span><span style="color:#89ddff;">} +</span><span style="color:#89ddff;">} +</span></code></pre> +<p><code>self.0</code> accesses the Producer’s first value in the tuple – the buffered queue Arc, to access its fields and methods.</p> +<p>We first get the <code>queue_is_full</code> boolean value and check whether the queue is full. Code execution will be paused until the queue has space and <code>queue_is_full</code> equals <code>false</code>. The <code>wait</code> method takes a MutexGuard and atomically releases the lock. This enables other threads to update its value. It re-acquires the lock before returning.</p> +<p>We access the internal queue if there is space, push the new element and call the <code>signal_queue_changes</code> method that we will define on <code>BufferedQueue</code> later.</p> +<p>We will also implement the <a href="https://doc.rust-lang.org/rust-by-example/trait/drop.html" title="https://doc.rust-lang.org/rust-by-example/trait/drop.html">Drop</a> trait, which will perform cleanup after our producer is out of scope:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">impl</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;</span><span> Drop </span><span style="color:#c792ea;">for </span><span>Producer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">drop</span><span style="color:#89ddff;">(&amp;</span><span style="color:#c792ea;">mut </span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">) { +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>elements_processed</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">store</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">, </span><span>Ordering</span><span style="color:#89ddff;">::</span><span>SeqCst</span><span style="color:#89ddff;">); +</span><span> </span><span style="color:#89ddff;">} +</span><span style="color:#89ddff;">} +</span></code></pre> +<p>We set <code>elements_processed</code> value to <code>true</code>, indicating that the producer has processed all its elements and is going out of scope. The <code>Drop</code> trait ensures that this implementation detail remains associated with the producer.</p> +<p>The <code>store</code> method requires a memory ordering, which defines how application memory is organized and ensures that our code avoids race conditions and improper data access across threads. We use the strongest possible ordering, <code>SeqCst</code>.</p> +<h3 id="consumer">Consumer</h3> +<p>Consumer’s methods are as follows:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">impl</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; </span><span>Consumer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> </span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">pop</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">) -&gt; </span><span style="color:#ffcb6b;">Option</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> queue_is_empty </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>is_empty</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="font-style:italic;color:#c792ea;">while </span><span style="color:#89ddff;">*</span><span>queue_is_empty </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>elements_processed</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">load</span><span style="color:#89ddff;">(</span><span>Ordering</span><span style="color:#89ddff;">::</span><span>SeqCst</span><span style="color:#89ddff;">) { +</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#ffcb6b;">None</span><span style="color:#89ddff;">; +</span><span> </span><span style="color:#89ddff;">} +</span><span> queue_is_empty </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>is_empty_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">wait</span><span style="color:#89ddff;">(</span><span>queue_is_empty</span><span style="color:#89ddff;">).</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> queue </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span>data</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> popped_element </span><span style="color:#89ddff;">=</span><span> queue</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">pop_front</span><span style="color:#89ddff;">(); +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">popped element</span><span style="color:#89ddff;">&quot;); +</span><span> +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span style="color:#82aaff;">signal_queue_changes</span><span style="color:#89ddff;">( +</span><span> queue</span><span style="color:#89ddff;">, +</span><span> Operation</span><span style="color:#89ddff;">::</span><span>Pop </span><span style="color:#89ddff;">{ +</span><span> is_empty_flag</span><span style="color:#89ddff;">:</span><span> queue_is_empty</span><span style="color:#89ddff;">, +</span><span> </span><span style="color:#89ddff;">}, +</span><span> </span><span style="color:#89ddff;">); +</span><span> popped_element +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">) -&gt; </span><span style="font-style:italic;color:#c792ea;">usize </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() +</span><span> </span><span style="color:#89ddff;">} +</span><span style="color:#89ddff;">} +</span></code></pre> +<p><code>pop</code> returns an <code>Option&lt;T&gt;</code> meaning it will return an enum variant <code>Some(T)</code> from the front of the queue, or <code>None</code> if the queue is empty. We wait for the producer to add elements if the queue is currently empty.</p> +<p>Our implementation guarantees that the queue will only pop an element from front of the queue if there is at least one element. We only return <code>None</code> once <code>elements_processed</code> is <code>true</code>, signalling that we can finish our execution.</p> +<h2 id="defining-bufferedqueue-logic">Defining BufferedQueue Logic</h2> +<p>We will first write a function to create a new buffered queue:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">buffered_queue</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;(</span><span style="color:#c792ea;">mut </span><span style="color:#f78c6c;">capacity</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">usize</span><span style="color:#89ddff;">) -&gt; (</span><span>Producer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;</span><span>, Consumer</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;) { +</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> capacity </span><span style="color:#89ddff;">&lt; </span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">{ +</span><span> eprintln!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">capacity cannot be lower than 1, defaulting to 1...</span><span style="color:#89ddff;">&quot;); +</span><span> capacity </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">1 +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> buffered_queue </span><span style="color:#89ddff;">=</span><span> BufferedQueue </span><span style="color:#89ddff;">{ +</span><span> data</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span>VecDeque</span><span style="color:#89ddff;">::</span><span>with_capacity</span><span style="color:#89ddff;">(</span><span>capacity</span><span style="color:#89ddff;">)), +</span><span> capacity</span><span style="color:#89ddff;">, +</span><span> is_full</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">false</span><span style="color:#89ddff;">), +</span><span> is_empty</span><span style="color:#89ddff;">: </span><span>Mutex</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">), +</span><span> is_full_signal</span><span style="color:#89ddff;">: </span><span>Condvar</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(), +</span><span> is_empty_signal</span><span style="color:#89ddff;">: </span><span>Condvar</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(), +</span><span> elements_processed</span><span style="color:#89ddff;">: </span><span>AtomicBool</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">false</span><span style="color:#89ddff;">), +</span><span> </span><span style="color:#89ddff;">}; +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> data </span><span style="color:#89ddff;">= </span><span>Arc</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(</span><span>buffered_queue</span><span style="color:#89ddff;">); +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> producer </span><span style="color:#89ddff;">=</span><span> Producer</span><span style="color:#89ddff;">(</span><span>data</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">clone</span><span style="color:#89ddff;">()); +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> consumer </span><span style="color:#89ddff;">=</span><span> Consumer</span><span style="color:#89ddff;">(</span><span>data</span><span style="color:#89ddff;">); +</span><span> +</span><span> </span><span style="color:#89ddff;">(</span><span>producer</span><span style="color:#89ddff;">,</span><span> consumer</span><span style="color:#89ddff;">) +</span><span style="color:#89ddff;">} +</span></code></pre> +<p><code>buffered_queue</code> takes a capacity and returns a tuple of Producer and Consumer types. It uses 1 as default if the capacity is 0, wraps the buffered queue value in Arc for cheap referencing and thread-safety, makes a reference copy and passes the Arc instances to Producer and Consumer types.</p> +<p>Now we will implement its methods:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">impl</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; </span><span>BufferedQueue</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt; { +</span><span> </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">) -&gt; </span><span style="font-style:italic;color:#c792ea;">usize </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> queue </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>data</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> queue</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">signal_queue_changes</span><span style="color:#89ddff;">(&amp;</span><span style="color:#f78c6c;">self</span><span>, </span><span style="color:#f78c6c;">queue</span><span style="color:#89ddff;">: </span><span>MutexGuard</span><span style="color:#89ddff;">&lt;</span><span>&#39;</span><span style="color:#89ddff;">_</span><span>, VecDeque</span><span style="color:#89ddff;">&lt;</span><span>T</span><span style="color:#89ddff;">&gt;&gt;</span><span>, </span><span style="color:#f78c6c;">operation</span><span style="color:#89ddff;">:</span><span> Operation</span><span style="color:#89ddff;">) { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> is_empty </span><span style="color:#89ddff;">=</span><span> queue</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() == </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">; +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> is_full </span><span style="color:#89ddff;">=</span><span> queue</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() == </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>capacity</span><span style="color:#89ddff;">; +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">match</span><span> operation </span><span style="color:#89ddff;">{ +</span><span> Operation</span><span style="color:#89ddff;">::</span><span>Push </span><span style="color:#89ddff;">{ </span><span style="color:#c792ea;">mut</span><span> is_full_flag </span><span style="color:#89ddff;">} =&gt; { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> is_empty_flag </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_empty</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span style="color:#89ddff;">*</span><span>is_empty_flag </span><span style="color:#89ddff;">{ +</span><span> </span><span style="color:#89ddff;">*</span><span>is_empty_flag </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">false</span><span style="color:#89ddff;">; +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">set is_empty to false</span><span style="color:#89ddff;">&quot;); +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_empty_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">notify_all</span><span style="color:#89ddff;">(); +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> is_full </span><span style="color:#89ddff;">{ +</span><span> </span><span style="color:#89ddff;">*</span><span>is_full_flag </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">; +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_full_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">notify_all</span><span style="color:#89ddff;">(); +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">set is_full to true</span><span style="color:#89ddff;">&quot;); +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> Operation</span><span style="color:#89ddff;">::</span><span>Pop </span><span style="color:#89ddff;">{ </span><span style="color:#c792ea;">mut</span><span> is_empty_flag </span><span style="color:#89ddff;">} =&gt; { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> is_full_flag </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_full</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">lock</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span style="color:#89ddff;">*</span><span>is_full_flag </span><span style="color:#89ddff;">{ +</span><span> </span><span style="color:#89ddff;">*</span><span>is_full_flag </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">false</span><span style="color:#89ddff;">; +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">set is_full to false</span><span style="color:#89ddff;">&quot;); +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_full_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">notify_all</span><span style="color:#89ddff;">(); +</span><span> </span><span style="color:#89ddff;">} +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> is_empty </span><span style="color:#89ddff;">{ +</span><span> </span><span style="color:#89ddff;">*</span><span>is_empty_flag </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">; +</span><span> </span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">.</span><span>is_empty_signal</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">notify_all</span><span style="color:#89ddff;">(); +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">set is_empty to true</span><span style="color:#89ddff;">&quot;); +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">} +</span><span style="color:#89ddff;">} +</span></code></pre> +<p>This method accepts the internal queue and operation enum types. <code>queue</code> defines the double-ended queue value after acquiring its mutex lock.</p> +<p>We match the operation variants and define their associated boolean values as mutable. Rust allows us to shorthand values if the variable name matches the field name, so we can write <code>{ mut is_full_flag: is_full_flag }</code> as  <code>{ mut is_full_flag }</code> and so on.</p> +<p>The method checks whether the queue’s state has changed: after an element <code>Push</code>, whether the queue is now full and whether it was empty earlier, after an element <code>Pop</code>, whether the queue is now empty and whether it was full before. It notifies waiting threads on the state changes if these conditions match, by calling the Condvars’ <code>notify_all</code> method.</p> +<h3 id="testing-things-out">Testing Things Out</h3> +<p>We can now test the functionality by creating a small simulation.</p> +<p>Add the following imports to the top of the <code>src/main.rs</code> file:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#c792ea;">use </span><span>buffered_queue_rs</span><span style="color:#89ddff;">::</span><span>buffered_queue</span><span style="color:#89ddff;">; +</span><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>thread</span><span style="color:#89ddff;">::{</span><span style="font-style:italic;color:#ff5370;">self</span><span style="color:#89ddff;">,</span><span> sleep</span><span style="color:#89ddff;">}; +</span><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>time</span><span style="color:#89ddff;">::</span><span>Duration</span><span style="color:#89ddff;">; +</span></code></pre> +<p>Write the following code in the <code>src/main.rs</code> file and replace the existing <code>main</code> function:</p> +<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">main</span><span style="color:#89ddff;">() { +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#89ddff;">(</span><span>producer</span><span style="color:#89ddff;">,</span><span> consumer</span><span style="color:#89ddff;">) = </span><span style="color:#82aaff;">buffered_queue</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">3</span><span style="color:#89ddff;">); +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> output </span><span style="color:#89ddff;">= </span><span style="color:#ffcb6b;">Vec</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">(); +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> producer_handle </span><span style="color:#89ddff;">= </span><span>thread</span><span style="color:#89ddff;">::</span><span>spawn</span><span style="color:#89ddff;">(</span><span style="color:#c792ea;">move </span><span style="color:#89ddff;">|| { +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">initializing producer thread...</span><span style="color:#89ddff;">&quot;); +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">for</span><span> num </span><span style="color:#89ddff;">in </span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">..=</span><span style="color:#f78c6c;">5 </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> processed_num </span><span style="color:#89ddff;">=</span><span> num </span><span style="color:#89ddff;">*</span><span> num </span><span style="color:#89ddff;">*</span><span> num</span><span style="color:#89ddff;">; +</span><span> +</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// mock processing behaviour +</span><span> </span><span style="color:#82aaff;">sleep</span><span style="color:#89ddff;">(</span><span>Duration</span><span style="color:#89ddff;">::</span><span>from_millis</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">250</span><span style="color:#89ddff;">)); +</span><span> producer</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">push</span><span style="color:#89ddff;">(</span><span>processed_num</span><span style="color:#89ddff;">); +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">}); +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> consumer_handle </span><span style="color:#89ddff;">= </span><span>thread</span><span style="color:#89ddff;">::</span><span>spawn</span><span style="color:#89ddff;">(</span><span style="color:#c792ea;">move </span><span style="color:#89ddff;">|| { +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">initializing consumer thread...</span><span style="color:#89ddff;">&quot;); +</span><span> +</span><span> </span><span style="font-style:italic;color:#c792ea;">loop </span><span style="color:#89ddff;">{ +</span><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#ffcb6b;">Some</span><span style="color:#89ddff;">(</span><span>num</span><span style="color:#89ddff;">) =</span><span> consumer</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">pop</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">else </span><span style="color:#89ddff;">{ +</span><span> println!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">exhausted queue, terminating consumer!</span><span style="color:#89ddff;">\n&quot;); +</span><span> </span><span style="font-style:italic;color:#c792ea;">return</span><span style="color:#89ddff;">; +</span><span> </span><span style="color:#89ddff;">}; +</span><span> +</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// mock processing behaviour +</span><span> </span><span style="color:#82aaff;">sleep</span><span style="color:#89ddff;">(</span><span>Duration</span><span style="color:#89ddff;">::</span><span>from_millis</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">400</span><span style="color:#89ddff;">)); +</span><span> output</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">push</span><span style="color:#89ddff;">(</span><span>num</span><span style="color:#89ddff;">); +</span><span> +</span><span> println!</span><span style="color:#89ddff;">( +</span><span> </span><span style="color:#89ddff;">&quot;</span><span style="color:#c3e88d;">pushed to output num: </span><span>{}</span><span style="color:#c3e88d;">; output_vec len: </span><span>{}</span><span style="color:#89ddff;">&quot;, +</span><span> num</span><span style="color:#89ddff;">, +</span><span> output</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">len</span><span style="color:#89ddff;">() +</span><span> </span><span style="color:#89ddff;">); +</span><span> </span><span style="color:#89ddff;">} +</span><span> </span><span style="color:#89ddff;">}); +</span><span> +</span><span> producer_handle</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">join</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span> consumer_handle</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">join</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">unwrap</span><span style="color:#89ddff;">(); +</span><span style="color:#89ddff;">} +</span></code></pre> +<p>We initialize our <code>producer</code> and <code>consumer</code> values by calling <code>buffered_queue</code>, and create a vector for the output produced by the consumer thread.</p> +<p>Then we mark our threads with <code>move</code>, meaning they will take ownership of any values used inside them. We use closures to write the thread logic inside the <code>spawn</code> blocks.</p> +<p>The producer thread iterates over a range of numbers, mocking input processing flow and pushes values to the queue. Meanwhile, the consumer thread processes values received from the <code>pop</code> function, stopping when it receives <code>None</code>, which is the signal to terminate execution.  </p> +<p>Finally, we receive return values of type <a href="https://doc.rust-lang.org/std/thread/struct.JoinHandle.html" title="https://doc.rust-lang.org/std/thread/struct.JoinHandle.html">JoinHandle</a> from the spawned threads and call <code>join</code> on them in the main thread. This ensures that it waits for the other threads to finish before exiting. The <code>unwrap</code> call will propagate any runtime errors in these threads to the main thread.</p> +<p>Running <code>cargo run</code> will output the following:</p> +<pre data-lang="plaintext" style="background-color:#212121;color:#eeffff;" class="language-plaintext "><code class="language-plaintext" data-lang="plaintext"><span>initializing consumer thread... +</span><span>initializing producer thread... +</span><span>pushed element +</span><span>set is_empty to false +</span><span>popped element +</span><span>set is_empty to true +</span><span>pushed element +</span><span>set is_empty to false +</span><span>pushed to output num: 1; output_vec len: 1 +</span><span>popped element +</span><span>set is_empty to true +</span><span>pushed element +</span><span>set is_empty to false +</span><span>pushed element +</span><span>pushed to output num: 8; output_vec len: 2 +</span><span>popped element +</span><span>pushed element +</span><span>pushed to output num: 27; output_vec len: 3 +</span><span>popped element +</span><span>pushed to output num: 64; output_vec len: 4 +</span><span>popped element +</span><span>set is_empty to true +</span><span>pushed to output num: 125; output_vec len: 5 +</span><span>exhausted queue, terminating consumer! +</span></code></pre> +<h2 id="conclusion">Conclusion</h2> +<p>This was a rewarding exercise for me, as it helped me get more familiar with Rust and concurrency concepts in general. You can find the full code for the exercise <a href="https://github.com/dhruv-ahuja/buffered-queue-rs" title="https://github.com/dhruv-ahuja/buffered-queue-rs">here</a>, there are some differences in the code shown here and in the repo.</p> +<p>Thanks for reading my post, any feedback or advice would be appreciated! You can write to me at <a href="mailto:dhruvahuja2k@gmail.com" title="mailto:dhruvahuja2k@gmail.com">my email</a>.</p> + + + + + Making My First Open-Source Contribution + 2023-06-11T00:00:00+00:00 + 2023-06-11T00:00:00+00:00 + + Unknown + + + https://dhruvahuja.me/posts/first-open-source-contribution/ + + <h2 id="intro">Intro</h2> +<p>The title is a bit devious – it’s not my first open-source contribution per se. I have made a couple of documentation fixes but those don’t count. I just made my first proper, meaningful open-source contribution on June 9th, 2023. This is a small but still meaningful step for me in the right direction, as someone who wants to get going with making significant open-source contributions in the near future. </p> +<p>My contribution to the theme I chose for my website was – I fixed a small issue on the light mode of the theme, and integrated a light-and-dark mode toggle button. Before diving into the nitty-gritty, let's go through some recent events that led to my contribution.</p> +<p>I wanted to setup a personal website to use as a space for publishing my thoughts and ideas. Also, I felt that it was about time I setup a site after all. I have recently been learning Rust and have been enjoying the process so I thought I should go with a Rust-based implementation. Upon some basic research I found out about Zola, a Static Site Generator that is fast and easy to get going with, so the choice wasn’t a difficult one.</p> +<h2 id="selecting-the-theme-and-making-changes">Selecting the Theme and Making Changes</h2> +<p>So with my decision of using Zola finalized, I looked at the themes list on the site, and there’s one thing I must concede – the number of themes on display is not a lot. I found a couple of themes that I liked and decided to explore the one I found the most appealing as well as content-focused - <a href="https://github.com/not-matthias/apollo" title="https://github.com/not-matthias/apollo">Apollo</a> which is what the site’s current theme is based on.</p> +<p>On fiddling around with the its code, I found out that the social icons weren't loading properly for the light mode. It used an inversion filter to invert their colours from black to white for the site’s dark mode, but the property was active even with the light mode.</p> +<p>So I opened an issue regarding the same and forked the repository in the meantime. I also found <a href="https://github.com/XXXMrG/archie-zola/" title="https://github.com/XXXMrG/archie-zola/">Archie-Zola</a>, the theme on which Apollo is based, to have a pleasing neon-green primary aesthetic that I liked more. I also found out that it had a dark and light mode toggle button, which I then wanted to implement in my own fork as well. So I began working on making these changes. </p> +<p>I fixed the problem with the icons’ disappearance relatively quickly, I just needed to move the relevant logic from the main file to the dark mode’s SASS file. Properly integrating the theme toggle button was much more complex and it required me to spend some time understanding the main visual logic of the two themes, and then writing the code, helping me learn about some core frontend styling logic. </p> +<h2 id="making-the-contribution">Making The Contribution</h2> +<p>My talks with Apollo's creator by this time had also gone well. He encouraged me to open a Pull Request. I then briefly updated him about my fork and asked him whether he would like to have the toggle button in Apollo and he had a positive response again. </p> +<p>I then created a new branch in my fork, made the requisite changes and opened the <a href="https://github.com/not-matthias/apollo/pull/20" title="https://github.com/not-matthias/apollo/pull/20">PR</a>.  It was reviewed and merged promptly, meaning I had made my first proper open-source contribution. </p> +<h2 id="my-experience">My Experience</h2> +<p>Although this is just a small instance, I had a lot of fun with the whole thing. I had to read up on several less-explored topics. I also got familiar with how the theme operates internally, which has led to me making more tweaks for my website. </p> +<p>I hope this can serve as encouragement for anyone reading this post, to take a look at open-source software and possibly support or take part in the development process. </p> +<p>My plan now is to delve deeper into Rust, learn more about the language and hopefully make more contributions :D</p> + + + + diff --git a/fonts.css b/fonts.css new file mode 100644 index 0000000..d91d51b --- /dev/null +++ b/fonts.css @@ -0,0 +1 @@ +@font-face{font-family:"Jetbrains Mono";font-style:normal;font-weight:400;src:url("../fonts/JetbrainsMono/JetBrainsMono-Regular.ttf"),local("ttf");font-display:swap}@font-face{font-family:"Space Grotesk";font-style:normal;font-weight:400;src:url("../fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf"),local("ttf");font-display:swap} \ No newline at end of file diff --git a/fonts/.gitkeep b/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/fonts/JetbrainsMono/JetBrainsMono-Bold.ttf b/fonts/JetbrainsMono/JetBrainsMono-Bold.ttf new file mode 100644 index 0000000..710c34b Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Bold.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-BoldItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-BoldItalic.ttf new file mode 100644 index 0000000..fdff00f Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-BoldItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-ExtraBold.ttf b/fonts/JetbrainsMono/JetBrainsMono-ExtraBold.ttf new file mode 100644 index 0000000..eb94300 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-ExtraBold.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-ExtraBoldItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-ExtraBoldItalic.ttf new file mode 100644 index 0000000..b70b4e7 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-ExtraBoldItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-ExtraLight.ttf b/fonts/JetbrainsMono/JetBrainsMono-ExtraLight.ttf new file mode 100644 index 0000000..74efced Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-ExtraLight.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-ExtraLightItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-ExtraLightItalic.ttf new file mode 100644 index 0000000..1a9d2d3 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-ExtraLightItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-Italic.ttf b/fonts/JetbrainsMono/JetBrainsMono-Italic.ttf new file mode 100644 index 0000000..ffd5d77 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Italic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-Light.ttf b/fonts/JetbrainsMono/JetBrainsMono-Light.ttf new file mode 100644 index 0000000..c0682f9 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Light.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-LightItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-LightItalic.ttf new file mode 100644 index 0000000..f20bac9 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-LightItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-Medium.ttf b/fonts/JetbrainsMono/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000..17ff945 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Medium.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-MediumItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-MediumItalic.ttf new file mode 100644 index 0000000..9b699bb Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-MediumItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-Regular.ttf b/fonts/JetbrainsMono/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..9a5202e Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Regular.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-SemiBold.ttf b/fonts/JetbrainsMono/JetBrainsMono-SemiBold.ttf new file mode 100644 index 0000000..84b7795 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-SemiBold.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-SemiBoldItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-SemiBoldItalic.ttf new file mode 100644 index 0000000..ffa1f39 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-SemiBoldItalic.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-Thin.ttf b/fonts/JetbrainsMono/JetBrainsMono-Thin.ttf new file mode 100644 index 0000000..1317bfe Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-Thin.ttf differ diff --git a/fonts/JetbrainsMono/JetBrainsMono-ThinItalic.ttf b/fonts/JetbrainsMono/JetBrainsMono-ThinItalic.ttf new file mode 100644 index 0000000..bc72439 Binary files /dev/null and b/fonts/JetbrainsMono/JetBrainsMono-ThinItalic.ttf differ diff --git a/fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf b/fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf new file mode 100644 index 0000000..869a60f Binary files /dev/null and b/fonts/SpaceGrotesk/SpaceGrotesk-Bold.ttf differ diff --git a/fonts/SpaceGrotesk/SpaceGrotesk-Light.ttf b/fonts/SpaceGrotesk/SpaceGrotesk-Light.ttf new file mode 100644 index 0000000..76a195f Binary files /dev/null and b/fonts/SpaceGrotesk/SpaceGrotesk-Light.ttf differ diff --git a/fonts/SpaceGrotesk/SpaceGrotesk-Medium.ttf b/fonts/SpaceGrotesk/SpaceGrotesk-Medium.ttf new file mode 100644 index 0000000..667905f Binary files /dev/null and b/fonts/SpaceGrotesk/SpaceGrotesk-Medium.ttf differ diff --git a/fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf b/fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf new file mode 100644 index 0000000..792fe1b Binary files /dev/null and b/fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf differ diff --git a/fonts/SpaceGrotesk/SpaceGrotesk-SemiBold.ttf b/fonts/SpaceGrotesk/SpaceGrotesk-SemiBold.ttf new file mode 100644 index 0000000..0219302 Binary files /dev/null and b/fonts/SpaceGrotesk/SpaceGrotesk-SemiBold.ttf differ diff --git a/images/mongodb_writes/poe_script_async.png b/images/mongodb_writes/poe_script_async.png new file mode 100644 index 0000000..3847518 Binary files /dev/null and b/images/mongodb_writes/poe_script_async.png differ diff --git a/images/mongodb_writes/poe_script_limited_memory_usage.png b/images/mongodb_writes/poe_script_limited_memory_usage.png new file mode 100644 index 0000000..b40c005 Binary files /dev/null and b/images/mongodb_writes/poe_script_limited_memory_usage.png differ diff --git a/images/mongodb_writes/poe_script_memory_usage.png b/images/mongodb_writes/poe_script_memory_usage.png new file mode 100644 index 0000000..4347842 Binary files /dev/null and b/images/mongodb_writes/poe_script_memory_usage.png differ diff --git a/images/repository_secrets.png b/images/repository_secrets.png new file mode 100644 index 0000000..d2dc6f8 Binary files /dev/null and b/images/repository_secrets.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..a18834c --- /dev/null +++ b/index.html @@ -0,0 +1,3 @@ + + dhruv-ahuja +

Tech

Welcome to my website, my name is Dhruv and I’m a self-taught software engineer mostly interested in backend with a pinch of frontend engineering. I started programming in May of 2021 and started working professionally in July 2022. The first language that I learnt and the one that I work in primarily is Python, though I spent several months just nerding over Rust sometime back, proof of which can found in my blogs and projects. I am also exploring and practicing frontend technologies.

I am currently primarily hacking on backend_burger and learning about scalable backend concepts plus some computer science concepts as well.

Interests

I was learning Spanish and French before pivoting to tech, it has been two plus years now but I still listen to Spanish songs on a regular basis. Reading is a big passion of mine and I delve into long reading sessions frequently. I have sometime back finished the Witcher and Mistborn (1st generation) series.

I’m also into audio-gear – Creative Pebble V3 for the desk and the Truthear X Crinacle Zero, EQd to Project Red V3, for mostly all other occasions. Both of these options are great value products and I’m super happy with them, for the time being :P I have also recently upgraded to the RK84 V2 keyboard with Gateron brown switches, a bliss to type on and has helluva battery life.

Contact

You can write to me at my email, connect with me on LinkedIn and find my side-activity and projects on Github.

\ No newline at end of file diff --git a/js/count.js b/js/count.js new file mode 100644 index 0000000..7c504bc --- /dev/null +++ b/js/count.js @@ -0,0 +1,270 @@ +// GoatCounter: https://www.goatcounter.com +// This file (and *only* this file) is released under the ISC license: +// https://opensource.org/licenses/ISC +;(function() { + 'use strict'; + + if (window.goatcounter && window.goatcounter.vars) // Compatibility with very old version; do not use. + window.goatcounter = window.goatcounter.vars + else + window.goatcounter = window.goatcounter || {} + + // Load settings from data-goatcounter-settings. + var s = document.querySelector('script[data-goatcounter]') + if (s && s.dataset.goatcounterSettings) { + try { var set = JSON.parse(s.dataset.goatcounterSettings) } + catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) } + for (var k in set) + if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1) + window.goatcounter[k] = set[k] + } + + var enc = encodeURIComponent + + // Get all data we're going to send off to the counter endpoint. + var get_data = function(vars) { + var data = { + p: (vars.path === undefined ? goatcounter.path : vars.path), + r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer), + t: (vars.title === undefined ? goatcounter.title : vars.title), + e: !!(vars.event || goatcounter.event), + s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)], + b: is_bot(), + q: location.search, + } + + var rcb, pcb, tcb // Save callbacks to apply later. + if (typeof(data.r) === 'function') rcb = data.r + if (typeof(data.t) === 'function') tcb = data.t + if (typeof(data.p) === 'function') pcb = data.p + + if (is_empty(data.r)) data.r = document.referrer + if (is_empty(data.t)) data.t = document.title + if (is_empty(data.p)) data.p = get_path() + + if (rcb) data.r = rcb(data.r) + if (tcb) data.t = tcb(data.t) + if (pcb) data.p = pcb(data.p) + return data + } + + // Check if a value is "empty" for the purpose of get_data(). + var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' } + + // See if this looks like a bot; there is some additional filtering on the + // backend, but these properties can't be fetched from there. + var is_bot = function() { + // Headless browsers are probably a bot. + var w = window, d = document + if (w.callPhantom || w._phantom || w.phantom) + return 150 + if (w.__nightmare) + return 151 + if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate) + return 152 + if (navigator.webdriver) + return 153 + return 0 + } + + // Object to urlencoded string, starting with a ?. + var urlencode = function(obj) { + var p = [] + for (var k in obj) + if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false) + p.push(enc(k) + '=' + enc(obj[k])) + return '?' + p.join('&') + } + + // Show a warning in the console. + var warn = function(msg) { + if (console && 'warn' in console) + console.warn('goatcounter: ' + msg) + } + + // Get the endpoint to send requests to. + var get_endpoint = function() { + var s = document.querySelector('script[data-goatcounter]') + if (s && s.dataset.goatcounter) + return s.dataset.goatcounter + return (goatcounter.endpoint || window.counter) // counter is for compat; don't use. + } + + // Get current path. + var get_path = function() { + var loc = location, + c = document.querySelector('link[rel="canonical"][href]') + if (c) { // May be relative or point to different domain. + var a = document.createElement('a') + a.href = c.href + if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, '')) + loc = a + } + return (loc.pathname + loc.search) || '/' + } + + // Run function after DOM is loaded. + var on_load = function(f) { + if (document.body === null) + document.addEventListener('DOMContentLoaded', function() { f() }, false) + else + f() + } + + // Filter some requests that we (probably) don't want to count. + goatcounter.filter = function() { + if ('visibilityState' in document && document.visibilityState === 'prerender') + return 'visibilityState' + if (!goatcounter.allow_frame && location !== parent.location) + return 'frame' + if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/)) + return 'localhost' + if (!goatcounter.allow_local && location.protocol === 'file:') + return 'localfile' + if (localStorage && localStorage.getItem('skipgc') === 't') + return 'disabled with #toggle-goatcounter' + return false + } + + // Get URL to send to GoatCounter. + window.goatcounter.url = function(vars) { + var data = get_data(vars || {}) + if (data.p === null) // null from user callback. + return + data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control. + + var endpoint = get_endpoint() + if (!endpoint) + return warn('no endpoint found') + + return endpoint + urlencode(data) + } + + // Count a hit. + window.goatcounter.count = function(vars) { + var f = goatcounter.filter() + if (f) + return warn('not counting because of: ' + f) + + var url = goatcounter.url(vars) + if (!url) + return warn('not counting because path callback returned null') + + var img = document.createElement('img') + img.src = url + img.style.position = 'absolute' // Affect layout less. + img.style.bottom = '0px' + img.style.width = '1px' + img.style.height = '1px' + img.loading = 'eager' + img.setAttribute('alt', '') + img.setAttribute('aria-hidden', 'true') + + var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) } + img.addEventListener('load', rm, false) + document.body.appendChild(img) + } + + // Get a query parameter. + window.goatcounter.get_query = function(name) { + var s = location.search.substr(1).split('&') + for (var i = 0; i < s.length; i++) + if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0) + return s[i].substr(name.length + 1) + } + + // Track click events. + window.goatcounter.bind_events = function() { + if (!document.querySelectorAll) // Just in case someone uses an ancient browser. + return + + var send = function(elem) { + return function() { + goatcounter.count({ + event: true, + path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''), + title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''), + referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''), + }) + } + } + + Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) { + if (elem.dataset.goatcounterBound) + return + var f = send(elem) + elem.addEventListener('click', f, false) + elem.addEventListener('auxclick', f, false) // Middle click. + elem.dataset.goatcounterBound = 'true' + }) + } + + // Add a "visitor counter" frame or image. + window.goatcounter.visit_count = function(opt) { + on_load(function() { + opt = opt || {} + opt.type = opt.type || 'html' + opt.append = opt.append || 'body' + opt.path = opt.path || get_path() + opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')} + + opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?' + if (opt.no_branding) opt.attr['src'] += '&no_branding=1' + if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style) + if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start) + if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end) + + var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type] + if (!tag) + return warn('visit_count: unknown type: ' + opt.type) + + if (opt.type === 'html') { + opt.attr['frameborder'] = '0' + opt.attr['scrolling'] = 'no' + } + + var d = document.createElement(tag) + for (var k in opt.attr) + d.setAttribute(k, opt.attr[k]) + + var p = document.querySelector(opt.append) + if (!p) + return warn('visit_count: append not found: ' + opt.append) + p.appendChild(d) + }) + } + + // Make it easy to skip your own views. + if (location.hash === '#toggle-goatcounter') { + if (localStorage.getItem('skipgc') === 't') { + localStorage.removeItem('skipgc', 't') + alert('GoatCounter tracking is now ENABLED in this browser.') + } + else { + localStorage.setItem('skipgc', 't') + alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.') + } + } + + if (!goatcounter.no_onload) + on_load(function() { + // 1. Page is visible, count request. + // 2. Page is not yet visible; wait until it switches to 'visible' and count. + // See #487 + if (!('visibilityState' in document) || document.visibilityState === 'visible') + goatcounter.count() + else { + var f = function(e) { + if (document.visibilityState !== 'visible') + return + document.removeEventListener('visibilitychange', f) + goatcounter.count() + } + document.addEventListener('visibilitychange', f) + } + + if (!goatcounter.no_events) + goatcounter.bind_events() + }) +})(); + diff --git a/js/feather.min.js b/js/feather.min.js new file mode 100644 index 0000000..41e4f81 --- /dev/null +++ b/js/feather.min.js @@ -0,0 +1,17 @@ +!function (e, n) { "object" == typeof exports && "object" == typeof module ? module.exports = n() : "function" == typeof define && define.amd ? define([], n) : "object" == typeof exports ? exports.feather = n() : e.feather = n() }("undefined" != typeof self ? self : this, function () { + return function (e) { var n = {}; function i(l) { if (n[l]) return n[l].exports; var t = n[l] = { i: l, l: !1, exports: {} }; return e[l].call(t.exports, t, t.exports, i), t.l = !0, t.exports } return i.m = e, i.c = n, i.d = function (e, n, l) { i.o(e, n) || Object.defineProperty(e, n, { configurable: !1, enumerable: !0, get: l }) }, i.r = function (e) { Object.defineProperty(e, "__esModule", { value: !0 }) }, i.n = function (e) { var n = e && e.__esModule ? function () { return e.default } : function () { return e }; return i.d(n, "a", n), n }, i.o = function (e, n) { return Object.prototype.hasOwnProperty.call(e, n) }, i.p = "", i(i.s = 61) }([function (e, n, i) { var l = i(20)("wks"), t = i(11), r = i(1).Symbol, o = "function" == typeof r; (e.exports = function (e) { return l[e] || (l[e] = o && r[e] || (o ? r : t)("Symbol." + e)) }).store = l }, function (e, n) { var i = e.exports = "undefined" != typeof window && window.Math == Math ? window : "undefined" != typeof self && self.Math == Math ? self : Function("return this")(); "number" == typeof __g && (__g = i) }, function (e, n) { var i = e.exports = { version: "2.5.6" }; "number" == typeof __e && (__e = i) }, function (e, n) { var i = {}.hasOwnProperty; e.exports = function (e, n) { return i.call(e, n) } }, function (e, n, i) { e.exports = !i(27)(function () { return 7 != Object.defineProperty({}, "a", { get: function () { return 7 } }).a }) }, function (e, n, i) { var l = i(13); e.exports = function (e) { if (!l(e)) throw TypeError(e + " is not an object!"); return e } }, function (e, n, i) { var l = i(5), t = i(56), r = i(55), o = Object.defineProperty; n.f = i(4) ? Object.defineProperty : function (e, n, i) { if (l(e), n = r(n, !0), l(i), t) try { return o(e, n, i) } catch (e) { } if ("get" in i || "set" in i) throw TypeError("Accessors not supported!"); return "value" in i && (e[n] = i.value), e } }, function (e, n, i) { var l = i(6), t = i(12); e.exports = i(4) ? function (e, n, i) { return l.f(e, n, t(1, i)) } : function (e, n, i) { return e[n] = i, e } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var l = o(i(35)), t = o(i(33)), r = o(i(32)); function o(e) { return e && e.__esModule ? e : { default: e } } n.default = Object.keys(t.default).map(function (e) { return new l.default(e, t.default[e], r.default[e]) }).reduce(function (e, n) { return e[n.name] = n, e }, {}) }, function (e, n, i) { var l = i(20)("keys"), t = i(11); e.exports = function (e) { return l[e] || (l[e] = t(e)) } }, function (e, n) { e.exports = {} }, function (e, n) { var i = 0, l = Math.random(); e.exports = function (e) { return "Symbol(".concat(void 0 === e ? "" : e, ")_", (++i + l).toString(36)) } }, function (e, n) { e.exports = function (e, n) { return { enumerable: !(1 & e), configurable: !(2 & e), writable: !(4 & e), value: n } } }, function (e, n) { e.exports = function (e) { return "object" == typeof e ? null !== e : "function" == typeof e } }, function (e, n) { e.exports = function (e) { if (void 0 == e) throw TypeError("Can't call method on " + e); return e } }, function (e, n) { var i = Math.ceil, l = Math.floor; e.exports = function (e) { return isNaN(e = +e) ? 0 : (e > 0 ? l : i)(e) } }, function (e, n, i) { + var l; + /*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames + */ + /*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames + */ + !function () { "use strict"; var i = function () { function e() { } function n(e, n) { for (var i = n.length, l = 0; l < i; ++l)t(e, n[l]) } e.prototype = Object.create(null); var i = {}.hasOwnProperty; var l = /\s+/; function t(e, t) { if (t) { var r = typeof t; "string" === r ? function (e, n) { for (var i = n.split(l), t = i.length, r = 0; r < t; ++r)e[i[r]] = !0 }(e, t) : Array.isArray(t) ? n(e, t) : "object" === r ? function (e, n) { for (var l in n) i.call(n, l) && (e[l] = !!n[l]) }(e, t) : "number" === r && function (e, n) { e[n] = !0 }(e, t) } } return function () { for (var i = arguments.length, l = Array(i), t = 0; t < i; t++)l[t] = arguments[t]; var r = new e; n(r, l); var o = []; for (var a in r) r[a] && o.push(a); return o.join(" ") } }(); void 0 !== e && e.exports ? e.exports = i : void 0 === (l = function () { return i }.apply(n, [])) || (e.exports = l) }() + }, function (e, n, i) { var l = i(14); e.exports = function (e) { return Object(l(e)) } }, function (e, n, i) { var l = i(6).f, t = i(3), r = i(0)("toStringTag"); e.exports = function (e, n, i) { e && !t(e = i ? e : e.prototype, r) && l(e, r, { configurable: !0, value: n }) } }, function (e, n) { e.exports = "constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",") }, function (e, n, i) { var l = i(2), t = i(1), r = t["__core-js_shared__"] || (t["__core-js_shared__"] = {}); (e.exports = function (e, n) { return r[e] || (r[e] = void 0 !== n ? n : {}) })("versions", []).push({ version: l.version, mode: i(29) ? "pure" : "global", copyright: "© 2018 Denis Pushkarev (zloirock.ru)" }) }, function (e, n, i) { var l = i(15), t = Math.min; e.exports = function (e) { return e > 0 ? t(l(e), 9007199254740991) : 0 } }, function (e, n) { var i = {}.toString; e.exports = function (e) { return i.call(e).slice(8, -1) } }, function (e, n, i) { var l = i(48), t = i(14); e.exports = function (e) { return l(t(e)) } }, function (e, n, i) { var l = i(54); e.exports = function (e, n, i) { if (l(e), void 0 === n) return e; switch (i) { case 1: return function (i) { return e.call(n, i) }; case 2: return function (i, l) { return e.call(n, i, l) }; case 3: return function (i, l, t) { return e.call(n, i, l, t) } }return function () { return e.apply(n, arguments) } } }, function (e, n, i) { var l = i(1), t = i(7), r = i(3), o = i(11)("src"), a = Function.toString, c = ("" + a).split("toString"); i(2).inspectSource = function (e) { return a.call(e) }, (e.exports = function (e, n, i, a) { var y = "function" == typeof i; y && (r(i, "name") || t(i, "name", n)), e[n] !== i && (y && (r(i, o) || t(i, o, e[n] ? "" + e[n] : c.join(String(n)))), e === l ? e[n] = i : a ? e[n] ? e[n] = i : t(e, n, i) : (delete e[n], t(e, n, i))) })(Function.prototype, "toString", function () { return "function" == typeof this && this[o] || a.call(this) }) }, function (e, n, i) { var l = i(13), t = i(1).document, r = l(t) && l(t.createElement); e.exports = function (e) { return r ? t.createElement(e) : {} } }, function (e, n) { e.exports = function (e) { try { return !!e() } catch (e) { return !0 } } }, function (e, n, i) { var l = i(1), t = i(2), r = i(7), o = i(25), a = i(24), c = function (e, n, i) { var y, p, h, x, s = e & c.F, u = e & c.G, d = e & c.S, f = e & c.P, v = e & c.B, g = u ? l : d ? l[n] || (l[n] = {}) : (l[n] || {}).prototype, m = u ? t : t[n] || (t[n] = {}), M = m.prototype || (m.prototype = {}); for (y in u && (i = n), i) h = ((p = !s && g && void 0 !== g[y]) ? g : i)[y], x = v && p ? a(h, l) : f && "function" == typeof h ? a(Function.call, h) : h, g && o(g, y, h, e & c.U), m[y] != h && r(m, y, x), f && M[y] != h && (M[y] = h) }; l.core = t, c.F = 1, c.G = 2, c.S = 4, c.P = 8, c.B = 16, c.W = 32, c.U = 64, c.R = 128, e.exports = c }, function (e, n) { e.exports = !1 }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var l = Object.assign || function (e) { for (var n = 1; n < arguments.length; n++) { var i = arguments[n]; for (var l in i) Object.prototype.hasOwnProperty.call(i, l) && (e[l] = i[l]) } return e }, t = o(i(16)), r = o(i(8)); function o(e) { return e && e.__esModule ? e : { default: e } } n.default = function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; if ("undefined" == typeof document) throw new Error("`feather.replace()` only works in a browser environment."); var n = document.querySelectorAll("[data-feather]"); Array.from(n).forEach(function (n) { return function (e) { var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, i = function (e) { return Array.from(e.attributes).reduce(function (e, n) { return e[n.name] = n.value, e }, {}) }(e), o = i["data-feather"]; delete i["data-feather"]; var a = r.default[o].toSvg(l({}, n, i, { class: (0, t.default)(n.class, i.class) })), c = (new DOMParser).parseFromString(a, "image/svg+xml").querySelector("svg"); e.parentNode.replaceChild(c, e) }(n, e) }) } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var l, t = i(8), r = (l = t) && l.__esModule ? l : { default: l }; n.default = function (e) { var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; if (console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."), !e) throw new Error("The required `key` (icon name) parameter is missing."); if (!r.default[e]) throw new Error("No icon matching '" + e + "'. See the complete list of icons at https://feathericons.com"); return r.default[e].toSvg(n) } }, function (e) { e.exports = { activity: ["pulse", "health", "action", "motion"], airplay: ["stream", "cast", "mirroring"], "alert-circle": ["warning"], "alert-octagon": ["warning"], "alert-triangle": ["warning"], "at-sign": ["mention"], award: ["achievement", "badge"], aperture: ["camera", "photo"], bell: ["alarm", "notification"], "bell-off": ["alarm", "notification", "silent"], bluetooth: ["wireless"], "book-open": ["read"], book: ["read", "dictionary", "booklet", "magazine"], bookmark: ["read", "clip", "marker", "tag"], briefcase: ["work", "bag", "baggage", "folder"], clipboard: ["copy"], clock: ["time", "watch", "alarm"], "cloud-drizzle": ["weather", "shower"], "cloud-lightning": ["weather", "bolt"], "cloud-rain": ["weather"], "cloud-snow": ["weather", "blizzard"], cloud: ["weather"], codepen: ["logo"], codesandbox: ["logo"], coffee: ["drink", "cup", "mug", "tea", "cafe", "hot", "beverage"], command: ["keyboard", "cmd"], compass: ["navigation", "safari", "travel"], copy: ["clone", "duplicate"], "corner-down-left": ["arrow"], "corner-down-right": ["arrow"], "corner-left-down": ["arrow"], "corner-left-up": ["arrow"], "corner-right-down": ["arrow"], "corner-right-up": ["arrow"], "corner-up-left": ["arrow"], "corner-up-right": ["arrow"], "credit-card": ["purchase", "payment", "cc"], crop: ["photo", "image"], crosshair: ["aim", "target"], database: ["storage"], delete: ["remove"], disc: ["album", "cd", "dvd", "music"], "dollar-sign": ["currency", "money", "payment"], droplet: ["water"], edit: ["pencil", "change"], "edit-2": ["pencil", "change"], "edit-3": ["pencil", "change"], eye: ["view", "watch"], "eye-off": ["view", "watch"], "external-link": ["outbound"], facebook: ["logo"], "fast-forward": ["music"], figma: ["logo", "design", "tool"], film: ["movie", "video"], "folder-minus": ["directory"], "folder-plus": ["directory"], folder: ["directory"], frown: ["emoji", "face", "bad", "sad", "emotion"], gift: ["present", "box", "birthday", "party"], "git-branch": ["code", "version control"], "git-commit": ["code", "version control"], "git-merge": ["code", "version control"], "git-pull-request": ["code", "version control"], github: ["logo", "version control"], gitlab: ["logo", "version control"], global: ["world", "browser", "language", "translate"], "hard-drive": ["computer", "server"], hash: ["hashtag", "number", "pound"], headphones: ["music", "audio"], heart: ["like", "love"], "help-circle": ["question mark"], hexagon: ["shape", "node.js", "logo"], home: ["house"], image: ["picture"], inbox: ["email"], instagram: ["logo", "camera"], key: ["password", "login", "authentication"], "life-bouy": ["help", "life ring", "support"], linkedin: ["logo"], lock: ["security", "password"], "log-in": ["sign in", "arrow"], "log-out": ["sign out", "arrow"], mail: ["email"], "map-pin": ["location", "navigation", "travel", "marker"], map: ["location", "navigation", "travel"], maximize: ["fullscreen"], "maximize-2": ["fullscreen", "arrows"], meh: ["emoji", "face", "neutral", "emotion"], menu: ["bars", "navigation", "hamburger"], "message-circle": ["comment", "chat"], "message-square": ["comment", "chat"], "mic-off": ["record"], mic: ["record"], minimize: ["exit fullscreen"], "minimize-2": ["exit fullscreen", "arrows"], monitor: ["tv"], moon: ["dark", "night"], "more-horizontal": ["ellipsis"], "more-vertical": ["ellipsis"], "mouse-pointer": ["arrow", "cursor"], move: ["arrows"], navigation: ["location", "travel"], "navigation-2": ["location", "travel"], octagon: ["stop"], package: ["box"], paperclip: ["attachment"], pause: ["music", "stop"], "pause-circle": ["music", "stop"], "pen-tool": ["vector", "drawing"], play: ["music", "start"], "play-circle": ["music", "start"], plus: ["add", "new"], "plus-circle": ["add", "new"], "plus-square": ["add", "new"], pocket: ["logo", "save"], power: ["on", "off"], radio: ["signal"], rewind: ["music"], rss: ["feed", "subscribe"], save: ["floppy disk"], search: ["find", "magnifier", "magnifying glass"], send: ["message", "mail", "paper airplane"], settings: ["cog", "edit", "gear", "preferences"], shield: ["security"], "shield-off": ["security"], "shopping-bag": ["ecommerce", "cart", "purchase", "store"], "shopping-cart": ["ecommerce", "cart", "purchase", "store"], shuffle: ["music"], "skip-back": ["music"], "skip-forward": ["music"], slash: ["ban", "no"], sliders: ["settings", "controls"], smile: ["emoji", "face", "happy", "good", "emotion"], speaker: ["music"], star: ["bookmark", "favorite", "like"], sun: ["brightness", "weather", "light"], sunrise: ["weather"], sunset: ["weather"], tag: ["label"], target: ["bullseye"], terminal: ["code", "command line"], "thumbs-down": ["dislike", "bad"], "thumbs-up": ["like", "good"], "toggle-left": ["on", "off", "switch"], "toggle-right": ["on", "off", "switch"], trash: ["garbage", "delete", "remove"], "trash-2": ["garbage", "delete", "remove"], triangle: ["delta"], truck: ["delivery", "van", "shipping"], twitter: ["logo"], umbrella: ["rain", "weather"], "video-off": ["camera", "movie", "film"], video: ["camera", "movie", "film"], voicemail: ["phone"], volume: ["music", "sound", "mute"], "volume-1": ["music", "sound"], "volume-2": ["music", "sound"], "volume-x": ["music", "sound", "mute"], watch: ["clock", "time"], wind: ["weather", "air"], "x-circle": ["cancel", "close", "delete", "remove", "times"], "x-octagon": ["delete", "stop", "alert", "warning", "times"], "x-square": ["cancel", "close", "delete", "remove", "times"], x: ["cancel", "close", "delete", "remove", "times"], youtube: ["logo", "video", "play"], "zap-off": ["flash", "camera", "lightning"], zap: ["flash", "camera", "lightning"] } }, function (e) { e.exports = { activity: '', airplay: '', "alert-circle": '', "alert-octagon": '', "alert-triangle": '', "align-center": '', "align-justify": '', "align-left": '', "align-right": '', anchor: '', aperture: '', archive: '', "arrow-down-circle": '', "arrow-down-left": '', "arrow-down-right": '', "arrow-down": '', "arrow-left-circle": '', "arrow-left": '', "arrow-right-circle": '', "arrow-right": '', "arrow-up-circle": '', "arrow-up-left": '', "arrow-up-right": '', "arrow-up": '', "at-sign": '', award: '', "bar-chart-2": '', "bar-chart": '', "battery-charging": '', battery: '', "bell-off": '', bell: '', bluetooth: '', bold: '', "book-open": '', book: '', bookmark: '', box: '', briefcase: '', calendar: '', "camera-off": '', camera: '', cast: '', "check-circle": '', "check-square": '', check: '', "chevron-down": '', "chevron-left": '', "chevron-right": '', "chevron-up": '', "chevrons-down": '', "chevrons-left": '', "chevrons-right": '', "chevrons-up": '', chrome: '', circle: '', clipboard: '', clock: '', "cloud-drizzle": '', "cloud-lightning": '', "cloud-off": '', "cloud-rain": '', "cloud-snow": '', cloud: '', code: '', codepen: '', codesandbox: '', coffee: '', columns: '', command: '', compass: '', copy: '', "corner-down-left": '', "corner-down-right": '', "corner-left-down": '', "corner-left-up": '', "corner-right-down": '', "corner-right-up": '', "corner-up-left": '', "corner-up-right": '', cpu: '', "credit-card": '', crop: '', crosshair: '', database: '', delete: '', disc: '', "dollar-sign": '', "download-cloud": '', download: '', droplet: '', "edit-2": '', "edit-3": '', edit: '', "external-link": '', "eye-off": '', eye: '', facebook: '', "fast-forward": '', feather: '', figma: '', "file-minus": '', "file-plus": '', "file-text": '', file: '', film: '', filter: '', flag: '', "folder-minus": '', "folder-plus": '', folder: '', frown: '', gift: '', "git-branch": '', "git-commit": '', "git-merge": '', "git-pull-request": '', github: '', gitlab: '', globe: '', grid: '', "hard-drive": '', hash: '', headphones: '', heart: '', "help-circle": '', hexagon: '', home: '', image: '', inbox: '', info: '', instagram: '', italic: '', key: '', layers: '', layout: '', "life-buoy": '', "link-2": '', link: '', linkedin: '', list: '', loader: '', lock: '', "log-in": '', "log-out": '', mail: '', "map-pin": '', map: '', "maximize-2": '', maximize: '', meh: '', menu: '', "message-circle": '', "message-square": '', "mic-off": '', mic: '', "minimize-2": '', minimize: '', "minus-circle": '', "minus-square": '', minus: '', monitor: '', moon: '', "more-horizontal": '', "more-vertical": '', "mouse-pointer": '', move: '', music: '', "navigation-2": '', navigation: '', octagon: '', package: '', paperclip: '', "pause-circle": '', pause: '', "pen-tool": '', percent: '', "phone-call": '', "phone-forwarded": '', "phone-incoming": '', "phone-missed": '', "phone-off": '', "phone-outgoing": '', phone: '', "pie-chart": '', "play-circle": '', play: '', "plus-circle": '', "plus-square": '', plus: '', pocket: '', power: '', printer: '', radio: '', "refresh-ccw": '', "refresh-cw": '', repeat: '', rewind: '', "rotate-ccw": '', "rotate-cw": '', rss: '', save: '', scissors: '', search: '', send: '', server: '', settings: '', "share-2": '', share: '', "shield-off": '', shield: '', "shopping-bag": '', "shopping-cart": '', shuffle: '', sidebar: '', "skip-back": '', "skip-forward": '', slack: '', slash: '', sliders: '', smartphone: '', smile: '', speaker: '', square: '', star: '', "stop-circle": '', sun: '', sunrise: '', sunset: '', tablet: '', tag: '', target: '', terminal: '', thermometer: '', "thumbs-down": '', "thumbs-up": '', "toggle-left": '', "toggle-right": '', "trash-2": '', trash: '', trello: '', "trending-down": '', "trending-up": '', triangle: '', truck: '', tv: '', twitter: '', type: '', umbrella: '', underline: '', unlock: '', "upload-cloud": '', upload: '', "user-check": '', "user-minus": '', "user-plus": '', "user-x": '', user: '', users: '', "video-off": '', video: '', voicemail: '', "volume-1": '', "volume-2": '', "volume-x": '', volume: '', watch: '', "wifi-off": '', wifi: '', wind: '', "x-circle": '', "x-octagon": '', "x-square": '', x: '', youtube: '', "zap-off": '', zap: '', "zoom-in": '', "zoom-out": '' } }, function (e) { e.exports = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" } }, function (e, n, i) { "use strict"; Object.defineProperty(n, "__esModule", { value: !0 }); var l = Object.assign || function (e) { for (var n = 1; n < arguments.length; n++) { var i = arguments[n]; for (var l in i) Object.prototype.hasOwnProperty.call(i, l) && (e[l] = i[l]) } return e }, t = function () { function e(e, n) { for (var i = 0; i < n.length; i++) { var l = n[i]; l.enumerable = l.enumerable || !1, l.configurable = !0, "value" in l && (l.writable = !0), Object.defineProperty(e, l.key, l) } } return function (n, i, l) { return i && e(n.prototype, i), l && e(n, l), n } }(), r = a(i(16)), o = a(i(34)); function a(e) { return e && e.__esModule ? e : { default: e } } var c = function () { function e(n, i) { var t = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : []; !function (e, n) { if (!(e instanceof n)) throw new TypeError("Cannot call a class as a function") }(this, e), this.name = n, this.contents = i, this.tags = t, this.attrs = l({}, o.default, { class: "feather feather-" + n }) } return t(e, [{ key: "toSvg", value: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; return "" + this.contents + "" } }, { key: "toString", value: function () { return this.contents } }]), e }(); n.default = c }, function (e, n, i) { "use strict"; var l = o(i(8)), t = o(i(31)), r = o(i(30)); function o(e) { return e && e.__esModule ? e : { default: e } } e.exports = { icons: l.default, toSvg: t.default, replace: r.default } }, function (e, n, i) { var l = i(0)("iterator"), t = !1; try { var r = [7][l](); r.return = function () { t = !0 }, Array.from(r, function () { throw 2 }) } catch (e) { } e.exports = function (e, n) { if (!n && !t) return !1; var i = !1; try { var r = [7], o = r[l](); o.next = function () { return { done: i = !0 } }, r[l] = function () { return o }, e(r) } catch (e) { } return i } }, function (e, n, i) { var l = i(22), t = i(0)("toStringTag"), r = "Arguments" == l(function () { return arguments }()); e.exports = function (e) { var n, i, o; return void 0 === e ? "Undefined" : null === e ? "Null" : "string" == typeof (i = function (e, n) { try { return e[n] } catch (e) { } }(n = Object(e), t)) ? i : r ? l(n) : "Object" == (o = l(n)) && "function" == typeof n.callee ? "Arguments" : o } }, function (e, n, i) { var l = i(38), t = i(0)("iterator"), r = i(10); e.exports = i(2).getIteratorMethod = function (e) { if (void 0 != e) return e[t] || e["@@iterator"] || r[l(e)] } }, function (e, n, i) { "use strict"; var l = i(6), t = i(12); e.exports = function (e, n, i) { n in e ? l.f(e, n, t(0, i)) : e[n] = i } }, function (e, n, i) { var l = i(10), t = i(0)("iterator"), r = Array.prototype; e.exports = function (e) { return void 0 !== e && (l.Array === e || r[t] === e) } }, function (e, n, i) { var l = i(5); e.exports = function (e, n, i, t) { try { return t ? n(l(i)[0], i[1]) : n(i) } catch (n) { var r = e.return; throw void 0 !== r && l(r.call(e)), n } } }, function (e, n, i) { "use strict"; var l = i(24), t = i(28), r = i(17), o = i(42), a = i(41), c = i(21), y = i(40), p = i(39); t(t.S + t.F * !i(37)(function (e) { Array.from(e) }), "Array", { from: function (e) { var n, i, t, h, x = r(e), s = "function" == typeof this ? this : Array, u = arguments.length, d = u > 1 ? arguments[1] : void 0, f = void 0 !== d, v = 0, g = p(x); if (f && (d = l(d, u > 2 ? arguments[2] : void 0, 2)), void 0 == g || s == Array && a(g)) for (i = new s(n = c(x.length)); n > v; v++)y(i, v, f ? d(x[v], v) : x[v]); else for (h = g.call(x), i = new s; !(t = h.next()).done; v++)y(i, v, f ? o(h, d, [t.value, v], !0) : t.value); return i.length = v, i } }) }, function (e, n, i) { var l = i(3), t = i(17), r = i(9)("IE_PROTO"), o = Object.prototype; e.exports = Object.getPrototypeOf || function (e) { return e = t(e), l(e, r) ? e[r] : "function" == typeof e.constructor && e instanceof e.constructor ? e.constructor.prototype : e instanceof Object ? o : null } }, function (e, n, i) { var l = i(1).document; e.exports = l && l.documentElement }, function (e, n, i) { var l = i(15), t = Math.max, r = Math.min; e.exports = function (e, n) { return (e = l(e)) < 0 ? t(e + n, 0) : r(e, n) } }, function (e, n, i) { var l = i(23), t = i(21), r = i(46); e.exports = function (e) { return function (n, i, o) { var a, c = l(n), y = t(c.length), p = r(o, y); if (e && i != i) { for (; y > p;)if ((a = c[p++]) != a) return !0 } else for (; y > p; p++)if ((e || p in c) && c[p] === i) return e || p || 0; return !e && -1 } } }, function (e, n, i) { var l = i(22); e.exports = Object("z").propertyIsEnumerable(0) ? Object : function (e) { return "String" == l(e) ? e.split("") : Object(e) } }, function (e, n, i) { var l = i(3), t = i(23), r = i(47)(!1), o = i(9)("IE_PROTO"); e.exports = function (e, n) { var i, a = t(e), c = 0, y = []; for (i in a) i != o && l(a, i) && y.push(i); for (; n.length > c;)l(a, i = n[c++]) && (~r(y, i) || y.push(i)); return y } }, function (e, n, i) { var l = i(49), t = i(19); e.exports = Object.keys || function (e) { return l(e, t) } }, function (e, n, i) { var l = i(6), t = i(5), r = i(50); e.exports = i(4) ? Object.defineProperties : function (e, n) { t(e); for (var i, o = r(n), a = o.length, c = 0; a > c;)l.f(e, i = o[c++], n[i]); return e } }, function (e, n, i) { var l = i(5), t = i(51), r = i(19), o = i(9)("IE_PROTO"), a = function () { }, c = function () { var e, n = i(26)("iframe"), l = r.length; for (n.style.display = "none", i(45).appendChild(n), n.src = "javascript:", (e = n.contentWindow.document).open(), e.write("
Posted on

Intro

The title is a bit devious – it’s not my first open-source contribution per se. I have made a couple of documentation fixes but those don’t count. I just made my first proper, meaningful open-source contribution on June 9th, 2023. This is a small but still meaningful step for me in the right direction, as someone who wants to get going with making significant open-source contributions in the near future. 

My contribution to the theme I chose for my website was – I fixed a small issue on the light mode of the theme, and integrated a light-and-dark mode toggle button. Before diving into the nitty-gritty, let's go through some recent events that led to my contribution.

I wanted to setup a personal website to use as a space for publishing my thoughts and ideas. Also, I felt that it was about time I setup a site after all. I have recently been learning Rust and have been enjoying the process so I thought I should go with a Rust-based implementation. Upon some basic research I found out about Zola, a Static Site Generator that is fast and easy to get going with, so the choice wasn’t a difficult one.

Selecting the Theme and Making Changes

So with my decision of using Zola finalized, I looked at the themes list on the site, and there’s one thing I must concede – the number of themes on display is not a lot. I found a couple of themes that I liked and decided to explore the one I found the most appealing as well as content-focused - Apollo which is what the site’s current theme is based on.

On fiddling around with the its code, I found out that the social icons weren't loading properly for the light mode. It used an inversion filter to invert their colours from black to white for the site’s dark mode, but the property was active even with the light mode.

So I opened an issue regarding the same and forked the repository in the meantime. I also found Archie-Zola, the theme on which Apollo is based, to have a pleasing neon-green primary aesthetic that I liked more. I also found out that it had a dark and light mode toggle button, which I then wanted to implement in my own fork as well. So I began working on making these changes. 

I fixed the problem with the icons’ disappearance relatively quickly, I just needed to move the relevant logic from the main file to the dark mode’s SASS file. Properly integrating the theme toggle button was much more complex and it required me to spend some time understanding the main visual logic of the two themes, and then writing the code, helping me learn about some core frontend styling logic. 

Making The Contribution

My talks with Apollo's creator by this time had also gone well. He encouraged me to open a Pull Request. I then briefly updated him about my fork and asked him whether he would like to have the toggle button in Apollo and he had a positive response again. 

I then created a new branch in my fork, made the requisite changes and opened the PR.  It was reviewed and merged promptly, meaning I had made my first proper open-source contribution. 

My Experience

Although this is just a small instance, I had a lot of fun with the whole thing. I had to read up on several less-explored topics. I also got familiar with how the theme operates internally, which has led to me making more tweaks for my website. 

I hope this can serve as encouragement for anyone reading this post, to take a look at open-source software and possibly support or take part in the development process. 

My plan now is to delve deeper into Rust, learn more about the language and hopefully make more contributions :D

\ No newline at end of file diff --git a/posts/implementing-buffered-queue-in-rust/index.html b/posts/implementing-buffered-queue-in-rust/index.html new file mode 100644 index 0000000..04bed83 --- /dev/null +++ b/posts/implementing-buffered-queue-in-rust/index.html @@ -0,0 +1,211 @@ + + Implementing a Naive Buffered Queue in Rust + +
Posted on

Introduction

O’Reilly’s Programming Rust book walks us through optimizing a part of a pipeline, in Chapter 19 Concurrency. It explains how a channel-based pipeline can encounter slowdowns and high memory usage if one of the consumer threads is much slower than one of the producer threads. The producer keeps adding tasks to the queue, but the consumer is unable to consume them at a satisfactory pace. The queue will have a large amount of unconsumed data causing memory spikes. Defining fixed capacities will lower memory consumption in applications without affecting the latencies since the consumer already consumes at its own fixed pace.

I had known about queues but had never thought about them in a larger scope, so I thought attempting a custom implementation would be a good way to learn more. I received a lot of help from the Rust community for this project, allowing me to better understand the concepts and improve my code :)  

Overview

We will walk through the implementation of a simple multi-threaded, blocking, buffered queue. The Producer thread will push elements till the queue is at capacity, and block until the queue has space again. Similarly, the Consumer thread will consume elements till the queue is empty, and block until it has elements again. We do not persist the threads once the input stream is expended.

Declaring our Types

We can create a new project with cargo new buffered-queue-rs and put our queue logic in src/lib.rs, marking all code inside the file as library code. This makes it accessible to the whole project by importing it with the project name specified in the cargo new command.

Add the following imports to the file:

use std::collections::VecDeque;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Condvar, Mutex, MutexGuard};
+

Next, we will define the types for our buffered queue implementation:

pub struct Producer<T>(Arc<BufferedQueue<T>>);
+
+pub struct Consumer<T>(Arc<BufferedQueue<T>>);
+
+pub struct BufferedQueue<T> {
+    data: Mutex<VecDeque<T>>,
+    pub capacity: usize,
+    pub is_full: Mutex<bool>,
+    pub is_full_signal: Condvar,
+    pub is_empty: Mutex<bool>,
+    pub is_empty_signal: Condvar,
+    pub elements_processed: AtomicBool,
+}
+

These are generic types, signified by the type parameter <T>, and can be used with any type as we have not defined any constraints on the type T.

Producer and Consumer follow the NewType pattern, allowing us to specify special behaviour on the wrapped type. It will help us separate producer and consumer concerns.

All the defined types use an Arc, a special pointer type that enables cheap shared access to data. It also allows sharing its pointer values across threads, even though the wrapped value might not be shareable. It maintains a reference counter for each reference active in memory, similar to Python objects.

Our internal queue implementation data is a double-ended queue, held by a mutex to prevent data inconsistencies and enforce exclusive data access. capacity is the user-defined maximum capacity for our queue. usize data type ensures that the value cannot be negative. is_full and is_empty indicate the queue’s current state. They will be used by the is_full_signal and is_empty_signal Condvars to allow the producer and consumer threads to wait until the queue is in their desired state. elements_processed is an AtomicBool and is thread-safe.  

The Operation enum type will signal the queue’s state updates to listening threads. It maps to the queue’s push and pop operations:

enum Operation<'a> {
+    Push { is_full_flag: MutexGuard<'a, bool> },
+    Pop { is_empty_flag: MutexGuard<'a, bool> },
+}
+

Acquiring the lock on a mutex returns a MutexGuard, a thin wrapper around the value held by the mutex. The lifetime specifier <’a>  in the type definition indicates how long the boolean flags are going to stay in memory. They are now associated with the enum variants and their held locks will be unlocked when the enum variants go out of scope.

We can see Rust’s powerful enums here, as we can add data on individual variants like we would with a struct.

Defining Producer and Consumer Logic

Producer and consumer have a similar logical flow. Both have 2 methods, the len method is common to both types and wraps a call to BufferedQueue‘s len method.  

Producer

Producer’s implementation is:

impl<T> Producer<T> {
+    pub fn push(&self, value: T) {
+        let mut queue_is_full = self.0.is_full.lock().unwrap();
+        while *queue_is_full {
+            queue_is_full = self.0.is_full_signal.wait(queue_is_full).unwrap();
+        }
+
+        let mut queue = self.0.data.lock().unwrap();
+        queue.push_back(value);
+        println!("pushed element");
+
+        self.0.signal_queue_changes(
+            queue,
+            Operation::Push {
+                is_full_flag: queue_is_full,
+            },
+        );
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+}
+

self.0 accesses the Producer’s first value in the tuple – the buffered queue Arc, to access its fields and methods.

We first get the queue_is_full boolean value and check whether the queue is full. Code execution will be paused until the queue has space and queue_is_full equals false. The wait method takes a MutexGuard and atomically releases the lock. This enables other threads to update its value. It re-acquires the lock before returning.

We access the internal queue if there is space, push the new element and call the signal_queue_changes method that we will define on BufferedQueue later.

We will also implement the Drop trait, which will perform cleanup after our producer is out of scope:

impl<T> Drop for Producer<T> {
+    fn drop(&mut self) {
+        self.0.elements_processed.store(true, Ordering::SeqCst);
+    }
+}
+

We set elements_processed value to true, indicating that the producer has processed all its elements and is going out of scope. The Drop trait ensures that this implementation detail remains associated with the producer.

The store method requires a memory ordering, which defines how application memory is organized and ensures that our code avoids race conditions and improper data access across threads. We use the strongest possible ordering, SeqCst.

Consumer

Consumer’s methods are as follows:

impl<T> Consumer<T> {
+    pub fn pop(&self) -> Option<T> {
+        let mut queue_is_empty = self.0.is_empty.lock().unwrap();
+        while *queue_is_empty {
+            if self.0.elements_processed.load(Ordering::SeqCst) {
+                return None;
+            }
+            queue_is_empty = self.0.is_empty_signal.wait(queue_is_empty).unwrap();
+        }
+
+        let mut queue = self.0.data.lock().unwrap();
+        let popped_element = queue.pop_front();
+        println!("popped element");
+
+        self.0.signal_queue_changes(
+            queue,
+            Operation::Pop {
+                is_empty_flag: queue_is_empty,
+            },
+        );
+        popped_element
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+}
+

pop returns an Option<T> meaning it will return an enum variant Some(T) from the front of the queue, or None if the queue is empty. We wait for the producer to add elements if the queue is currently empty.

Our implementation guarantees that the queue will only pop an element from front of the queue if there is at least one element. We only return None once elements_processed is true, signalling that we can finish our execution.

Defining BufferedQueue Logic

We will first write a function to create a new buffered queue:

pub fn buffered_queue<T>(mut capacity: usize) -> (Producer<T>, Consumer<T>) {
+    if capacity < 1 {
+        eprintln!("capacity cannot be lower than 1, defaulting to 1...");
+        capacity = 1
+    }
+
+    let buffered_queue = BufferedQueue {
+        data: Mutex::new(VecDeque::with_capacity(capacity)),
+        capacity,
+        is_full: Mutex::new(false),
+        is_empty: Mutex::new(true),
+        is_full_signal: Condvar::new(),
+        is_empty_signal: Condvar::new(),
+        elements_processed: AtomicBool::new(false),
+    };
+
+    let data = Arc::new(buffered_queue);
+    let producer = Producer(data.clone());
+    let consumer = Consumer(data);
+
+    (producer, consumer)
+}
+

buffered_queue takes a capacity and returns a tuple of Producer and Consumer types. It uses 1 as default if the capacity is 0, wraps the buffered queue value in Arc for cheap referencing and thread-safety, makes a reference copy and passes the Arc instances to Producer and Consumer types.

Now we will implement its methods:

impl<T> BufferedQueue<T> {
+    fn len(&self) -> usize {
+        let queue = self.data.lock().unwrap();
+        queue.len()
+    }
+
+    fn signal_queue_changes(&self, queue: MutexGuard<'_, VecDeque<T>>, operation: Operation) {
+        let is_empty = queue.len() == 0;
+        let is_full = queue.len() == self.capacity;
+
+        match operation {
+            Operation::Push { mut is_full_flag } => {
+                let mut is_empty_flag = self.is_empty.lock().unwrap();
+                if *is_empty_flag {
+                    *is_empty_flag = false;
+                    println!("set is_empty to false");
+                    self.is_empty_signal.notify_all();
+                }
+
+                if is_full {
+                    *is_full_flag = true;
+                    self.is_full_signal.notify_all();
+                    println!("set is_full to true");
+                }
+            }
+
+            Operation::Pop { mut is_empty_flag } => {
+                let mut is_full_flag = self.is_full.lock().unwrap();
+                if *is_full_flag {
+                    *is_full_flag = false;
+                    println!("set is_full to false");
+                    self.is_full_signal.notify_all();
+                }
+
+                if is_empty {
+                    *is_empty_flag = true;
+                    self.is_empty_signal.notify_all();
+                    println!("set is_empty to true");
+                }
+            }
+        }
+    }
+}
+

This method accepts the internal queue and operation enum types. queue defines the double-ended queue value after acquiring its mutex lock.

We match the operation variants and define their associated boolean values as mutable. Rust allows us to shorthand values if the variable name matches the field name, so we can write { mut is_full_flag: is_full_flag } as  { mut is_full_flag } and so on.

The method checks whether the queue’s state has changed: after an element Push, whether the queue is now full and whether it was empty earlier, after an element Pop, whether the queue is now empty and whether it was full before. It notifies waiting threads on the state changes if these conditions match, by calling the Condvars’ notify_all method.

Testing Things Out

We can now test the functionality by creating a small simulation.

Add the following imports to the top of the src/main.rs file:

use buffered_queue_rs::buffered_queue;
+use std::thread::{self, sleep};
+use std::time::Duration;
+

Write the following code in the src/main.rs file and replace the existing main function:

fn main() {
+    let (producer, consumer) = buffered_queue(3);
+    let mut output = Vec::new();
+
+    let producer_handle = thread::spawn(move || {
+        println!("initializing producer thread...");
+
+        for num in 1..=5 {
+            let processed_num = num * num * num;
+
+            // mock processing behaviour
+            sleep(Duration::from_millis(250));
+            producer.push(processed_num);
+        }
+    });
+
+    let consumer_handle = thread::spawn(move || {
+        println!("initializing consumer thread...");
+
+        loop {
+            let Some(num) = consumer.pop() else {
+                    println!("exhausted queue, terminating consumer!\n");
+                    return;
+            };
+
+            // mock processing behaviour
+            sleep(Duration::from_millis(400));
+            output.push(num);
+
+            println!(
+                "pushed to output num: {}; output_vec len: {}",
+                num,
+                output.len()
+            );
+        }
+    });
+
+    producer_handle.join().unwrap();
+    consumer_handle.join().unwrap();
+}
+

We initialize our producer and consumer values by calling buffered_queue, and create a vector for the output produced by the consumer thread.

Then we mark our threads with move, meaning they will take ownership of any values used inside them. We use closures to write the thread logic inside the spawn blocks.

The producer thread iterates over a range of numbers, mocking input processing flow and pushes values to the queue. Meanwhile, the consumer thread processes values received from the pop function, stopping when it receives None, which is the signal to terminate execution.  

Finally, we receive return values of type JoinHandle from the spawned threads and call join on them in the main thread. This ensures that it waits for the other threads to finish before exiting. The unwrap call will propagate any runtime errors in these threads to the main thread.

Running cargo run will output the following:

initializing consumer thread...
+initializing producer thread...
+pushed element
+set is_empty to false
+popped element
+set is_empty to true
+pushed element
+set is_empty to false
+pushed to output num: 1; output_vec len: 1
+popped element
+set is_empty to true
+pushed element
+set is_empty to false
+pushed element
+pushed to output num: 8; output_vec len: 2
+popped element
+pushed element
+pushed to output num: 27; output_vec len: 3
+popped element
+pushed to output num: 64; output_vec len: 4
+popped element
+set is_empty to true
+pushed to output num: 125; output_vec len: 5
+exhausted queue, terminating consumer!
+

Conclusion

This was a rewarding exercise for me, as it helped me get more familiar with Rust and concurrency concepts in general. You can find the full code for the exercise here, there are some differences in the code shown here and in the repo.

Thanks for reading my post, any feedback or advice would be appreciated! You can write to me at my email.

\ No newline at end of file diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 0000000..c9a6193 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,4 @@ + + dhruv-ahuja + +
\ No newline at end of file diff --git a/posts/optimizing-mongo-writes/index.html b/posts/optimizing-mongo-writes/index.html new file mode 100644 index 0000000..d85baf4 --- /dev/null +++ b/posts/optimizing-mongo-writes/index.html @@ -0,0 +1,4 @@ + + A Story of Optimizing Mongo DB Writes + +
Posted on

Introduction

I am developing an application for the game Path of Exile, that will predict item prices based on the item quantity and the item price history throughout the season. It is a straightforward implementation that updates the prices for items at a fixed frequency, and requires items and their categories to be pre-populated in the database.

I am running my MongoDB server on the Atlas Free Tier, hosted on AWS.

The core flow is as follows: there are several categories for whom we already have general information prepared, we first create ItemCategory  documents with this information for each category. Then we fetch data for all items belonging to that category. The poe.ninja website caches its API responses and we’re able to quickly fetch the desired data even with relatively large responses. We initially made all these API calls in a loop, and the whole process was quite smooth as the response time is always quick. Upon getting the data and parsing each entity in the response array into Pydantic models, we then map the data in the form <category_name: item_data> where item_data is the list of items we fetched from the API. Do keep in mind that this flow will change as optimize the script down the line.

Pydantic & Its Usage Here  

We create either CurrencyItemEntity or  ItemEntity  Pydantic model instances for each entity in API responses, based on whether it belongs to Currency or the other Item type, as items in the Currency category have a separate API response schema. Pydantic helps maintain data integrity and validates the response data, making it easier to deal with potentially unstructured third-party data (although the APIs in this case are very consistent). There would definitely be an additional overhead for parsing the item data into a Pydantic model instance for each entity, but being able to enforce schema for third-party data in this case, and getting consistent type hint support is well worth it. Its performance has also been vastly improved with the version 2.0 release that happened late last year.

The Naive Approach: Single Insertions

The code for the naive approach and the first iteration of the script is available here. Here we are iterating over all categories, getting their response data and mapping them into the hashmap with category name as key, and the data array as value. It does not take much time to gather data for 31,000+ items, as mentioned above due to the quick API responses.

Calling save_item_data, It takes us an average of 1216 seconds or 20 minutes 16 seconds to parse each entity’s data, create and insert Item document instances and save them to the database one-by-one. I think this time is acceptable since the script meant to be run rarely, however it is practically very slow and not convenient. This makes extending the script or re-running it a chore. I am also interested in knowing how much time we can shave off from this, especially since there is a very simple optimization available. Memory usage for this approach would be high too, since we’re loading all item data entities in memory and have two objects for each entity. We will look into memory management after improving the execution time.

Each save call requires network round trips between the app and the database, and database processing time. These accumulate rapidly as we save a large number of documents one-by-one.

The Good Approach: Bulk Insertions

The modified script using approach is available here. I found using insertMany for bulk-inserts the most common and the most impactful approach, when I looked for improvement advice. Pushing all DB instances to an array and bulk-inserting them all at once, took us just ~10.7 seconds!  This is an incredible improvement and should be the first choice if you need to insert multiple documents.

The problem here is the memory usage, which peaks at roughly 350MB and only drops towards the end of the script, where we see memory being released.

Bulk-Inserts Memory Consumption

This can be verified by simply restricting the maximum length of the item_data array to 10,000, which would restrict the number of accessed item data records of the BaseType category, which has contains much more items. Making this change reduces the peak memory usage to ~285MB.

Bulk-Inserts Memory Consumption, Restricted Object Count

We can make one more improvement which will reduce both the memory usage and execution time, but requires a significant code refactor.

The Better Approach: Producer-Consumer Pattern

The mostly overhauled script using this approach is available here. We rewrote the main functions, moved API calls to their own functions, added more logging statements, handled errors and wrapped the async functions into async Tasks. These pass data using dedicated Queues and run until they get the termination signals using sentinel values.

Implementing Async Producer and Consumers means we now process information faster, by using different async tasks to concurrently get API data, parse that data, and save documents in bulk in the database.

This coordination allows us to reduce the time taken further to about 9 seconds, and all the tasks finish execution almost one after the other. This is an improvement of about 1.7 seconds over the bulk-insert implementation. We also witness a big drop in memory usage, with the peak memory usage being ~271MB, or an improvement of ~ 22.6% over the previous consumption of 350MB. These are fantastic results, in my opinion.

Optimal Approach Memory Consumption

Conclusion

This was a journey where I got hands-on with some general performance improvements for database writes and also implemented the common but very effective Producer-Consumer design pattern. I am sure that there are things that I missed and certain aspects that can be handled better, I’ll be keeping an eye out for any improvements.

It was a great learning and experimental experience for me, and I hope that this made a good read for you. Please do not hesitate to email me if you wish to discuss anything. I will be adding comments functionality to the site soon.

\ No newline at end of file diff --git a/posts/page/1/index.html b/posts/page/1/index.html new file mode 100644 index 0000000..005226c --- /dev/null +++ b/posts/page/1/index.html @@ -0,0 +1 @@ +Redirect

Click here to be redirected. \ No newline at end of file diff --git a/posts/tag-based-ci-cd-pipeline/index.html b/posts/tag-based-ci-cd-pipeline/index.html new file mode 100644 index 0000000..299e626 --- /dev/null +++ b/posts/tag-based-ci-cd-pipeline/index.html @@ -0,0 +1,117 @@ + + Tag-Based Python CI/CD Pipeline + +

Posted on

Introduction

I recently setup a CI/CD pipeline using GitHub Actions, to automate code quality management, testing and Docker image deployment for my Python webapp. The CI workflow triggers on every commit to the main branch and formats, lints and tests the code. It uses a Redis service container since the integration tests call the API endpoints, which use a caching layer before accessing the database. It also uses an action step to debug failures. The CD workflow runs on new tag pushes to the repository. Both workflows can also be run manually.

Let’s get started with the setup.

Initial Setup

Create a Docker Hub repository to push the images to, and generate a read-write scope Access Token for use with the workflows. Copy the token for use in the next step.

Next, setup environment secrets so that our application can access these values during the testing step. Go to the Settings → Secrets and variables → Actions panel in the GitHub repository and define the any repository secrets required during the workflow’s execution. Also define the Docker username and password secrets here. Use the access token generated above for DOCKER_PASSWORD.

Manage Repository Secrets

Creating the CI Workflow

Create a .github/workflows folder in your local codebase and a ci.yml  file, adding the following code at the top of the file:

name: CI
+
+on:
+    push:
+        branches: main
+    
+    workflow_dispatch: {}
+
+concurrency:
+    group: ${{ github.workflow }}-${{ github.ref }}
+    cancel-in-progress: true
+

This defines that the CI workflow runs only when code is pushed to the main branch. workflow_dispatch: {}  allows us to run the workflow manually from the Actions page. Our concurrency configuration ensures that the workflow’s runs are grouped together under one Git reference value and that only one run happens at a time. If a workflow is underway and another is triggered, the current run is cancelled in favour of the newer run.

Next, define the list of environment variables required by the application like so:

env:
+    DB_URL: ${{secrets.DB_URL}}
+    JWT_SECRET_KEY: ${{secrets.JWT_SECRET_KEY}}
+    AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
+    AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
+    AWS_REGION_NAME: ${{secrets.AWS_REGION_NAME}}
+    SQS_QUEUE_NAME: ${{secrets.SQS_QUEUE_NAME}}
+    S3_BUCKET_URL: ${{secrets.S3_BUCKET_URL}}
+    S3_BUCKET_NAME: ${{secrets.S3_BUCKET_NAME}}
+    S3_LOGS_FOLDER: ${{secrets.S3_LOGS_FOLDER}}
+    REDIS_HOST: localhost
+

We define environment variables by reading the repository secrets we set in the initial setup section, with the exception of REDIS_HOST, which is set to localhost to enable our application access to the Redis service.

Now comes the main part for the CI logic, the job itself:

jobs:
+    build:
+        runs-on: ubuntu-latest
+        
+        services:
+            # Label used to access the service container
+            redis:
+                image: redis
+                # Set health checks to wait until redis has started
+                options: >-
+                    --health-cmd "redis-cli ping"
+                    --health-interval 10s
+                    --health-timeout 5s
+                    --health-retries 5
+
+                ports:
+                    - 6379:6379
+
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v4
+
+            - name: Setup Python
+              uses: actions/setup-python@v4
+              with:
+                python-version: "3.11"
+                cache: "pip"
+            
+            - name: Install PyCurl Dependencies
+              run: 
+                sudo apt-get update && sudo apt-get install -y curl libcurl4-openssl-dev build-essential libssl-dev
+
+            - name: Install Dependencies
+              run:
+                python -m pip install --upgrade pip
+                pip install -r requirements.txt
+            
+            - name: Test code
+              run: 
+                pytest . -s -v -W ignore
+            
+            - name: Check Code Formatting
+              run:
+                ruff format --line-length=120 --check . 
+            
+            - name: Check Code Linting
+              run: 
+                ruff check .
+            
+            - name: Setup Tmate Session
+              if: ${{ failure() }}
+              uses: mxschmitt/action-tmate@v3
+

Let’s walk through the job’s specifics, step-by-step.

The Redis service logic sets up a Redis container with health check options to ensure that the workflow waits for it to boot up, and exposes port 6379 to make it accessible to the application when we run the tests.

Checkout makes the repository’s code available to the workflow, and Setup Python setups the specific Python version — 3.11 in our case — and caches the dependencies installed by pip to make future workflow runs faster. Install Pycurl Dependencies installs the dependencies required by the pycurl Python library on Ubuntu. The following step installs the Python dependencies used by our application.

The code testing step runs the pytest test suite gathers and runs all tests in the current directory. My project has a few unit tests and integration tests for each API endpoint. The -s flag outputs any Python print statements to the stdout stream, and -v runs the tests in verbose mode, giving us a detailed overview of the ongoing tests. I have added -W ignore to ignore the warnings emitted during the execution of the tests, primarily to help avoid the Pydantic v1 deprecation warnings issued for third party libraries.

I am using Ruff as the formatter and linter of choice, it is very fast and I feel that it has good linting rules without being overly restrictive. It is easy to setup formatting, lint and type checks in editors and is a one-time setup and I feel that it really helps keep the codebase maintainable in the long run.

The next two steps check for formatting and lint errors in the code and stop the workflow in case of any errors. This ensures that contributing developers adhere to Ruff’s code quality standards.

The last step is optional, and only runs if any of the previous steps fails. It allows us to ssh into the currently ongoing workflow session to check the environment and debug issues. Be careful though, it kept running for quite a while since I forgot to cancel the workflow run manually.  I am not sure if it has a time limit or it keeps running indefinitely.

Creating the CD workflow

 The CD pipeline is quite straightforward:

name: CD
+
+on:
+    push:
+        tags: "v*"
+
+    # allow manually triggering this workflow
+    workflow_dispatch: {}
+
+concurrency:
+    group: ${{ github.workflow }}-${{ github.ref }}
+    cancel-in-progress: true
+
+jobs:
+    deploy:
+        runs-on: ubuntu-latest
+
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v4
+
+            - name: Log into Docker Hub
+              uses: docker/login-action@v3
+              with:
+                username: ${{secrets.DOCKER_USERNAME}}
+                password: ${{secrets.DOCKER_PASSWORD}}
+
+            - name: Get Latest Tag
+              id: latest-tag
+              uses: "WyriHaximus/github-action-get-previous-tag@v1"
+              with:
+                fallback: latest
+
+            - name: Build and push Docker image
+              uses: docker/build-push-action@v5
+              with:
+                push: true
+                tags: dhruvahuja/backend_burger:${{ steps.latest-tag.outputs.tag }}
+                labels: ${{steps.latest-tag.outputs.tag}}
+

We define the workflow to either run manually or when a new tag prefixed by v is pushed to the repository, example. v0.0.1Checkout is required to allow getting git tag in the third step. The next step reads the Docker username and password from repository secrets and logs us into Docker Hub. Get Latest Tag reads the tag which was just pushed, if the workflow was triggered by a tag push, otherwise defaulting to latest.

The final step builds the Docker image with the version tag and label, and pushes it to the Docker repository URL, defined in the tags directive. In this case, dhruvahuja/backend_burger.

Conclusion

That’s it, our CI/CD pipeline is ready! There are Actions available for all sort of use-cases, and you can remove or add steps according to your needs. For example, you may choose to ssh into a server after the Build and push Docker image step to pull and run the new image. I did not add this particular step since I did not have a need for it at the moment.

I chose the tag-based approach for the deployment process since I wanted to deploy new images only on specific milestones, which I can manage with version tags.

\ No newline at end of file diff --git a/posts/writing-rust-bindings/index.html b/posts/writing-rust-bindings/index.html new file mode 100644 index 0000000..860f3dc --- /dev/null +++ b/posts/writing-rust-bindings/index.html @@ -0,0 +1,51 @@ + + Writing Rust Bindings for My Python App + +
Posted on

Introduction

spoti-dl, a Python-based CLI song downloading tool was the first “proper” application that I developed. It acted as a proof-of-concept of my programming skills as a self-taught developer, and helped me land my first job. However, it lacked some basic features, mainly- no parallel downloads for albums and playlists.

I recently added a few new features and re-wrote its core functionality in Rust, as I have been enjoying working with Rust’s robust type system, compiler-level error handling and syntax.

Development

Development was relatively smooth for the most part, as the app logic is straightforward — you accept and parse the input Spotify link, the CLI flag parameters and process downloads. I figured out general things by googling and/or through some experimentation, such as the trait implementations to parse CLI flags from Strings into enums and vice-versa. The lazy_static macro helped me allocate a static HashSet containing disallowed characters for files and folder names, on runtime. I also became more comfortable with bound traits and experienced the power of generics. I was able to use the following function across all of my download flows, as it accepts any input P that can be referenced as Path and any input S that can be converted into a String type:

pub fn add_metadata<P, S>(
+    file_path: P,
+    album_art_path: P,
+    simple_song: spotify::SimpleSong,
+    album_name: S,
+) where
+    P: AsRef<Path> + Debug,
+    S: Into<String>,
+{...}
+

I mainly struggled when implementing the async logic to download songs in parallel, due to my inexperience with writing async code in Rust. I had to spend a lot of time working with the compiler’s restrictions and Tokio’s ’static + Send requirements for spawning tasks, as its work-stealing scheduler model means that a task running in one thread could be picked up by another thread. I used tokio::task::block_in_place to wrap the add_metadata function call as the lofty crate does not support async.

I added a CLI flag, allowing users to specify the number of tasks to use to process parallel downloads, and used batch downloads of 100 songs for playlists, as they can contain several thousands of songs.

The following is the core async logic for parallel downloads — calculate songs to be downloaded by each task, make Arcs to pass cheap, shareable clones for certain values, chunk the list of songs and create and wait for the spawned tasks to finish downloads:

let parallel_tasks: usize = if album.songs.len() >= cli_args.parallel_downloads as usize {
+    cli_args.parallel_downloads as usize
+} else {
+    album.songs.len()
+};
+
+let songs_per_task = album.songs.len() / parallel_tasks;
+let remaining_songs = album.songs.len() % parallel_tasks;
+
+let cli_args = Arc::new(cli_args);
+let album_art_dir = Arc::new(album_art_dir);
+let album_name = Arc::new(album.name);
+
+let mut handles = Vec::with_capacity(parallel_tasks);
+let mut start = 0;
+
+for i in 0..parallel_tasks {
+    let mut end = start + songs_per_task;
+    if i < remaining_songs {
+        end += 1
+    }
+
+    let songs_chunk = &album.songs[start..end];
+    let handle = tokio::spawn(download_songs(
+        file_path.clone(),
+        cli_args.clone(),
+        album_art_dir.clone(),
+        album_name.clone(),
+        songs_chunk.to_vec(),
+    ));
+
+    start = end;
+    handles.push(handle)
+}
+
+for handle in handles {
+    handle.await?;
+}
+

Tooling

I dropped Poetry as it would not be compatible with the Rust bindings and used simple virtual environments for dependency management, and Twine for distributing built wheels.

Pyo3 acts as the bridge between the parent Python code that calls a single exposed Rust function and enables all the inter-op between the two systems. Maturin compiles the Rust code into a Python library, and also compiles both codebases into a distributable Python wheel.

The following is a list of changes I had to make in my Cargo and pyproject TOML files, to ensure that the build process and pip installed package worked as intended:

  • Maturin did not recognize the project as a mixed Python-Rust project, hence did not include Rust code in the distributable Python wheel. Setting lib.name table’s value to match Python source directory (spotidl) in Cargo.toml fixed this error.

  • pyproject.toml required several modifications — I needed to set the project.scripts value to spoti-dl = "spotidl.main:main", partially because the project name (spoti-dl) and Python source directory names were different. I also added the python-packages = ["spotidl"] value under tool.maturin to ensure its inclusion during the build process. I also had to add my dependencies and relevant project metadata in their apt sections, after dropping Poetry.

  • Maturin compiles the Rust code as a library inside our Python source directory. It adds an underscore _ to the library’s name by default, which is quite confusing. I rectified this by configuring the module-name value under tool.maturin.

I faced several problems when attempting to build wheels for Linux using Docker, on my M1 MacBook. I must have easily spent 15-20 hours trying to get the openssl-sys crate to compile as it was the single point of failure, using both the python manylinux and maturin Docker images. I tried to integrate a CI/CD setup using GitHub Actions too, but to no avail, as the crate kept failing to compile. You can check the graveyard of my CI’s failed runs here. Eventually I had to settle for manually compiling wheels on Linux, Mac and Windows and copying them to a folder before publishing them with Twine.

Conclusion

This was a rewarding experience for me, as I dealt with efficiently processing large amounts of data and sharpened my skills with Rust and Tokio.

I witnessed a 20-25% speed increase and 50% less memory consumption in my Rust code when downloading a single song. The development process was smooth as Pyo3 and Maturin are very well-documented and provide convenient APIs, make it incredibly easy to get started with writing FFIs for Python.

\ No newline at end of file diff --git a/projects/backend-burger/index.html b/projects/backend-burger/index.html new file mode 100644 index 0000000..2b32045 --- /dev/null +++ b/projects/backend-burger/index.html @@ -0,0 +1,4 @@ + + backend_burger + +
\ No newline at end of file diff --git a/projects/buffered-queue-rs/index.html b/projects/buffered-queue-rs/index.html new file mode 100644 index 0000000..911dc28 --- /dev/null +++ b/projects/buffered-queue-rs/index.html @@ -0,0 +1,4 @@ + + buffered-queue-rs + +
\ No newline at end of file diff --git a/projects/index.html b/projects/index.html new file mode 100644 index 0000000..029af8a --- /dev/null +++ b/projects/index.html @@ -0,0 +1,4 @@ + + dhruv-ahuja + +

backend_burger

A backend application to learn and integrate best practices and advanced concepts

spoti-dl

CLI tool to download songs, albums and playlists, with metadata and album cover, using Spotify links

buffered-queue-rs

A custom implementation of a simple blocking, buffered queue in Rust

Password Manager GO

Simple password manager app in Golang to keep all your passwords in one place

Series-Lookup

Get Windows toast notifications if your locally saved shows of choice have a new season
\ No newline at end of file diff --git a/projects/password-manager/index.html b/projects/password-manager/index.html new file mode 100644 index 0000000..a701af2 --- /dev/null +++ b/projects/password-manager/index.html @@ -0,0 +1,4 @@ + + Password Manager GO + +
\ No newline at end of file diff --git a/projects/series-lookup/index.html b/projects/series-lookup/index.html new file mode 100644 index 0000000..2b9cf60 --- /dev/null +++ b/projects/series-lookup/index.html @@ -0,0 +1,4 @@ + + Series-Lookup + +
\ No newline at end of file diff --git a/projects/spoti-dl/index.html b/projects/spoti-dl/index.html new file mode 100644 index 0000000..e4c145c --- /dev/null +++ b/projects/spoti-dl/index.html @@ -0,0 +1,4 @@ + + spoti-dl + +
\ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..95c2ab8 --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: +Allow: / +Sitemap: https://dhruvahuja.me/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..0df5a9f --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,50 @@ + + + + https://dhruvahuja.me/ + + + https://dhruvahuja.me/posts/ + + + https://dhruvahuja.me/posts/first-open-source-contribution/ + 2023-06-11 + + + https://dhruvahuja.me/posts/implementing-buffered-queue-in-rust/ + 2023-09-06 + + + https://dhruvahuja.me/posts/optimizing-mongo-writes/ + 2024-05-21 + + + https://dhruvahuja.me/posts/page/1/ + + + https://dhruvahuja.me/posts/tag-based-ci-cd-pipeline/ + 2024-03-05 + + + https://dhruvahuja.me/posts/writing-rust-bindings/ + 2023-11-06 + + + https://dhruvahuja.me/projects/ + + + https://dhruvahuja.me/projects/backend-burger/ + + + https://dhruvahuja.me/projects/buffered-queue-rs/ + + + https://dhruvahuja.me/projects/password-manager/ + + + https://dhruvahuja.me/projects/series-lookup/ + + + https://dhruvahuja.me/projects/spoti-dl/ + + diff --git a/social_icons/LICENSE b/social_icons/LICENSE new file mode 100644 index 0000000..993facc --- /dev/null +++ b/social_icons/LICENSE @@ -0,0 +1 @@ +All icons in this directory are downloaded from [FontAwesome](https://fontawesome.com/). They are part of the [free offer](https://fontawesome.com/license/free) and are licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). \ No newline at end of file diff --git a/social_icons/apple.svg b/social_icons/apple.svg new file mode 100644 index 0000000..d0532d5 --- /dev/null +++ b/social_icons/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/bitcoin.svg b/social_icons/bitcoin.svg new file mode 100644 index 0000000..941d9b0 --- /dev/null +++ b/social_icons/bitcoin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/deviantart.svg b/social_icons/deviantart.svg new file mode 100644 index 0000000..7dbd0b6 --- /dev/null +++ b/social_icons/deviantart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/diaspora.svg b/social_icons/diaspora.svg new file mode 100644 index 0000000..55527b5 --- /dev/null +++ b/social_icons/diaspora.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/discord.svg b/social_icons/discord.svg new file mode 100644 index 0000000..f0dfeab --- /dev/null +++ b/social_icons/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/discourse.svg b/social_icons/discourse.svg new file mode 100644 index 0000000..343bea6 --- /dev/null +++ b/social_icons/discourse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/email.svg b/social_icons/email.svg new file mode 100644 index 0000000..85245e2 --- /dev/null +++ b/social_icons/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/ethereum.svg b/social_icons/ethereum.svg new file mode 100644 index 0000000..af202de --- /dev/null +++ b/social_icons/ethereum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/etsy.svg b/social_icons/etsy.svg new file mode 100644 index 0000000..ebc040a --- /dev/null +++ b/social_icons/etsy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/facebook.svg b/social_icons/facebook.svg new file mode 100644 index 0000000..0afaf7a --- /dev/null +++ b/social_icons/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/github.svg b/social_icons/github.svg new file mode 100644 index 0000000..e32807a --- /dev/null +++ b/social_icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/gitlab.svg b/social_icons/gitlab.svg new file mode 100644 index 0000000..b577d3f --- /dev/null +++ b/social_icons/gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/google.svg b/social_icons/google.svg new file mode 100644 index 0000000..b3776b0 --- /dev/null +++ b/social_icons/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/hacker-news.svg b/social_icons/hacker-news.svg new file mode 100644 index 0000000..23e3980 --- /dev/null +++ b/social_icons/hacker-news.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/instagram.svg b/social_icons/instagram.svg new file mode 100644 index 0000000..89f63c4 --- /dev/null +++ b/social_icons/instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/linkedin.svg b/social_icons/linkedin.svg new file mode 100644 index 0000000..d54fcf5 --- /dev/null +++ b/social_icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/mastodon.svg b/social_icons/mastodon.svg new file mode 100644 index 0000000..5e12f81 --- /dev/null +++ b/social_icons/mastodon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/paypal.svg b/social_icons/paypal.svg new file mode 100644 index 0000000..efdc81a --- /dev/null +++ b/social_icons/paypal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/pinterest.svg b/social_icons/pinterest.svg new file mode 100644 index 0000000..eb977c2 --- /dev/null +++ b/social_icons/pinterest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/quora.svg b/social_icons/quora.svg new file mode 100644 index 0000000..375d302 --- /dev/null +++ b/social_icons/quora.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/reddit.svg b/social_icons/reddit.svg new file mode 100644 index 0000000..a8a3a96 --- /dev/null +++ b/social_icons/reddit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/rss.svg b/social_icons/rss.svg new file mode 100644 index 0000000..b862886 --- /dev/null +++ b/social_icons/rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/skype.svg b/social_icons/skype.svg new file mode 100644 index 0000000..3369aba --- /dev/null +++ b/social_icons/skype.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/slack.svg b/social_icons/slack.svg new file mode 100644 index 0000000..0dbc26d --- /dev/null +++ b/social_icons/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/snapchat.svg b/social_icons/snapchat.svg new file mode 100644 index 0000000..2cd79dd --- /dev/null +++ b/social_icons/snapchat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/soundcloud.svg b/social_icons/soundcloud.svg new file mode 100644 index 0000000..4724d74 --- /dev/null +++ b/social_icons/soundcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/spotify.svg b/social_icons/spotify.svg new file mode 100644 index 0000000..1d393ba --- /dev/null +++ b/social_icons/spotify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/stack-exchange.svg b/social_icons/stack-exchange.svg new file mode 100644 index 0000000..0a3177f --- /dev/null +++ b/social_icons/stack-exchange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/stack-overflow.svg b/social_icons/stack-overflow.svg new file mode 100644 index 0000000..2ca50c7 --- /dev/null +++ b/social_icons/stack-overflow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/steam.svg b/social_icons/steam.svg new file mode 100644 index 0000000..b61f374 --- /dev/null +++ b/social_icons/steam.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/telegram.svg b/social_icons/telegram.svg new file mode 100644 index 0000000..02f48c0 --- /dev/null +++ b/social_icons/telegram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/twitter.svg b/social_icons/twitter.svg new file mode 100644 index 0000000..0778f72 --- /dev/null +++ b/social_icons/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/vimeo.svg b/social_icons/vimeo.svg new file mode 100644 index 0000000..d98368e --- /dev/null +++ b/social_icons/vimeo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/whatsapp.svg b/social_icons/whatsapp.svg new file mode 100644 index 0000000..d259142 --- /dev/null +++ b/social_icons/whatsapp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/social_icons/youtube.svg b/social_icons/youtube.svg new file mode 100644 index 0000000..287dca2 --- /dev/null +++ b/social_icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/dark.css b/theme/dark.css new file mode 100644 index 0000000..1512b93 --- /dev/null +++ b/theme/dark.css @@ -0,0 +1 @@ +:root.dark{--text-0: rgba(255, 255, 255, 87%);--text-1: rgba(255, 255, 255, 60%);--bg-0: #121212;--bg-1: rgba(255, 255, 255, 5%);--bg-2: rgba(23, 23, 23, 100%);--primary-color: #50fa7b;--hover-color: black}:root.dark time{color:#9c9a9a}:root.dark .social{filter:invert(1)}:root.dark .social:hover{filter:invert(0)}:root.dark a:hover{background-color:var(--primary-color);color:var(--hover-color)} \ No newline at end of file diff --git a/theme/light.css b/theme/light.css new file mode 100644 index 0000000..e82239d --- /dev/null +++ b/theme/light.css @@ -0,0 +1 @@ +:root{--text-0: rgba(0, 0, 0, 87%);--text-1: rgba(0, 0, 0, 66%);--bg-0: #fff;--bg-1: #f2f2f2;--bg-2: #fefefe;--primary-color: #50fa7b;--hover-color: black}:root time{color:gray}:root a:hover{background-color:var(--primary-color);color:var(--hover-color)} \ No newline at end of file