├── .gitignore ├── LICENSE ├── README.md ├── config.nims ├── docs ├── nimdoc.out.css └── timerpool.html ├── tests ├── humanecho.nim └── timerpool_test.nim ├── timerpool.nim └── timerpool.nimble /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache 2 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael Krauter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### timerpool library [![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble.png)](https://github.com/yglukhov/nimble-tag) 2 | Thread-safe software timerpool implementation in Nim. 3 | 4 | This library simplifies handling and running 5 | very much timers (constrained by memory). 6 | The timers itself are not designed for 7 | critical time-measuring purposes. It´s for 8 | event purpose only 9 | 10 | the module documentation can be found [here](https://mikra01.github.io/timerpool/timerpool.html) 11 | 12 | #### installation 13 | nimble install timerpool 14 | 15 | At the moment only tested on the windows10 platform with gcc/vcc (x64). 16 | 17 | Comments, bug reports and PR´s always welcome. 18 | 19 | to compile and run the tests type 20 | "nimble test" within the projects main directory after cloning the repository. The tests 21 | itself are not installed via the nimble installation 22 | 23 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | import ospaths, strutils 2 | 3 | task timerpool_tests, "run timerpool tests": 4 | withDir thisDir(): 5 | switch("threads","on") 6 | switch("run") 7 | setCommand "c", "tests/timerpool_test.nim" 8 | -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --border: #dde; 18 | --text: #222; 19 | --anchor: #07b; 20 | --anchor-focus: #607c9f; 21 | --input-focus: #1fa0eb; 22 | --strong: #3c3c3c; 23 | --hint: #9A9A9A; 24 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1NGQtOTVjNy1iOTgxMDFlMDFhMmEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzMzNGYwMWEtMjAxMS1hNTRkLTk1YzctYjk4MTAxZTAxYTJhIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MzMzNGYwMWEtMjAxMS1hNTRkLTk1YzctYjk4MTAxZTAxYTJhIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1NGQtOTVjNy1iOTgxMDFlMDFhMmEiIHN0RXZ0OndoZW49IjIwMTktMTItMDNUMDE6MDM6NDgrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4wIChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4PsixkAAAJ5klEQVR4nO2dfbBUZR3HP3vvxVD0zo0ACXxBuQMoQjJ1DfMl0NIhNcuSZqQhfGt6UWtK06xJexkrmywVRTQlHCIdtclC0zBJvYIvvEUgZpc3XyC7RVbKlQu1/fHdbc+uu2fPOfs85+y55/nMnBl2z+5zfnc5v/M8z+8119XVRYroAG4HfgvMT1YUR4MMAa4HLkhakCRoSVqAELwLeBY4C7gF+D6QS1QiR1ROAJ4Dzk9akKQwoXhtwL4GxvHjU8AKoNPz3leAu4HBFq+bAyZZHD9rDAK+BywDDklYlkQxoXhfAtYAEw2MVckQYBHwU6or99nA08BBFq49GngUeBIYaWH8rNEJdAOXA60Jy5I4jSreSOBKYDzwBPCJhiUqcSjwe2BWnc9NLnxuvMFrnwqsAqYBBwBfNzh2FpmNfs9jkhakWcg1aFxZiH5UL3cDnwf+Xue7BwFjgFHAOwuv24tyob3cO0LIshP4EbCn8Pq/wKvA9sLxMvCvOmPsA1yDZnHv/nEv2mM+F0IeR4m8z7lM7tMbUbzj0CxX7YfbAXwaWFJ4PRrNIu9FS9KJyEIZN68CG4DnkRJtLBw7gHHAYuDdNb77EDAjBhkHIk7xKoiqeK3IwjilzuceQJvoZjdQ/AMZaeoZiWYgBXSEwyleBW0Rv3cR9ZUO4LSI48fN2wN+bi5wJNBvUZaBSCaVy48oxpVhwDdMC5ISxpJRh6/DLGEUrxXt29YBQ+2IkwquR76ofZIWxJFegireNLSnm48skFmmDfmiVgJHJyuKI620ADOpbWEcDPwYOZKD7OmyxCTkXL+wzueOiEEWR8poQb60V4A7kLm/yFjgKeALuM1xLfYDbkX+zEGe98cAX0Oui6viF8vR7OS6urragW2UZr21wK+Aiwlu7XPoN3sYOAd4H6WH1SnA0qSEcjQnRT/e1bgnsw16kGPez4/lyCBF48oNwL+TFGSAsgCndI4qFBVvJ0owdZhjL3CnxfHzBo8+YBMyol0CHBijrKbHS/LoA7Yio9sPgJNr/QHekLGR6MffL+KP4SjnHmQxtoXNmbQP+CHyV75hYDzTIWNpWkU8iR5mq71vVsZqXgtcFqNQ/wG2IOtfD8oi6AX+Ujj+isKz8sBrnu+1okyGdmD/wnEgcDClTIdRyJRvI1cvCMciq7At4rj5eoCPAusbHCfLigda/VyKgi+AtyreMGAzykGzQQ/wO+BxSlkCuy1dq8hw5OieUjimYT+x9bHCdWwS1823Ez1EXmhgjKwrXpHzkduuanbCtzGX+NkPPAj8GincNkPjNkIO5dadUjiOB95m+BonopQpm8R58/0JJbHWy2eshVM8sRvdbyurKV4Hmoka2WA/iwwLP6d+QmzSdKC92GzK/W9R+Q3woQbHCELcN991wJcjftcpXolngKm18vFmoVonYcgDv0Qz5pqGREuOTuA8lPYUZbndh0LJNpkUqgZx33xvomim7RG+6xSvnOm1gqQXoyiMoKxFs8VZpFfpQHvQK4HDUPnAsBa9bxGP0tUjF+IYCkxFew+/G3owdq20pgjzt3uPRscs/o43IaOhH2f4ZaAPRyZQP6vgbuCbyGext87F0sgIZFI/N8BnlwBnolovcWAjq/uzwM0+55cBJ0UYN84ZL+rfbnLMM4FfUDv7Z1XlCe8FetETbleNL7+CZrnvMjCVDuTOOA84Hf+96ga0PC8qXY50FQsuMg+41+d8p885R4n7gdt8zo+qvDkmUF4fZQXwEbS+99KDMhlWkw0eALqQglXyDDCdcovf+4lv5jPNXJ9zWc/FDMMdPudGVCreRlTWwVtWbynwYVQQCFSp61Q042WJLUjB1nneuw8tvXo97x1Lugvg+j1Mo9boySLVHtJFWqsthx5GlbSGeN5bigrHdqPl52Zj4qWLXvTQWY4KOX2ccgPMBLRcuy9+0YzhguXN4GuYq2Zc2R/NZg+hfYt3/9ZCepdQthmB4vIWIYOTbWyWzGt2Y0izG1fqjlltxnsdpbPMRMmd3lqTTumqMw7FZY5G5mSHw5dalreiRWYGWjbZ7gYUlFa0xOtIWA4vk1E6zWEoI+FvyYrjSAO1FG8DCmQGKd+DJFsGogWVVFiP/GWbga9Svg9NgtPQvnd04fUNCcriSBF+vqZ5nn9PQ+Xs4q401oI6EP0R+BkyXoAeAtcgBfwidnvkVaMVFTO6n1JoWTfqiONw1MVP8e6l3GVwOPJZXW5VItGGiuduAu5CZdOrMQJ1CHqpIFccS+LxaD/3Hcr7vF0Xw7UdAwQ/xduLGkJ6aUMhVAuwU006B3wM+ZLmozJ5QRhWkGs9yjKw1fhwDsq8eE/F+y+i1CeHIxD1wppupXrA5xyUOjQHMzU3cyjTeS2aaaN2Fzoc1bhch3xspuqBTkDulQVUz1q4mYEbNuewQD3FexGFS1VjOLoRHwOOinj9HAooXY2CSidHHKeSI5GFcRWNdSxqR7VH1iHHeTV24R+X53C8hSCBvPPqnD8B+AOygn6OYAm0ORSGthLl8B0d4DtRmIKsoMsJF1U/Hi1dt6DusIN8PrsIlUdwOAITpDFlC6q3MTbgmHm011qGepOvQSXPipyOCujW6rxqk0dRWYsVFe8PRSn5JxWOoEvdfOGzfnF5tnCRK+bGi33MoB1hL0U5d1H5J5oVD6A5mp8sQS6KSWh5e0jEcR4BPmhKqJA4xTM3XuxjBlW8DuRacDU3y0myNbNTPHPjxT5m0GTN15A/zVFiI+HKYzgc/ydMlrRfgmQWuYn0F91xJEQYxVuDnMcOrQAWJi2EI72ErQviwqLEQpQ+5XBEIqzi3YWLwF+BMiMcjshEqYR1Gdk1KmxBsaR9SQviSDdRFK8fxVU+YliWZmcbcq7vSFoQR/qJWvuxD0WgLDYoSzPzAqowtjVhORwDhEaKru4GPoliGgcyy4Hj0DLT4TBCo9WO88jQ8Bns97lLghvRTOfqqDiMYqrM+HyUYdBtaLykeRmlK12C9rQOh1FM1vd/HqUIzaT5e+LVoh/VxByHShs6HFaw0VjjHhTxP5d0LT+fRnu5q3HuAodlbHW02Q5cDByM+sw1642cRylCx6PeZiuTFScUFxK+f19QovaRS+t4tsasxhvABbZbSfUCV6CM7qtQl6Fm4E1U22UqcAYqvZ42fgJMxH6vdYc5nkBlSW6Pq4fbS6hb6jg0u9yGug7FyS5U1+UcVBbwbFSuMM1sQ1bXK4A9CcviqM0e9H80HdUxCpwIa4McygA/GfgAcCJqmGKKXUixupEv7nHsLc2agWNQ0d9OzC+PHNHIo1XeLCoe8kkqXiUtwKFoWXoEKqk3BpWLaC8cXsV8HT1J+tFTZKvn+DMqFZi1knvtyKg1O2lBHADcCVxEedNSAP4HJcsr0NNWHVUAAAAASUVORK5CYII="); 25 | 26 | --keyword: #5e8f60; 27 | --identifier: #222; 28 | --comment: #484a86; 29 | --operator: #155da4; 30 | --punctuation: black; 31 | --other: black; 32 | --escapeSequence: #c4891b; 33 | --number: #252dbe; 34 | --literal: #a4255b; 35 | --raw-data: #a4255b; 36 | } 37 | 38 | [data-theme="dark"] { 39 | --primary-background: #171921; 40 | --secondary-background: #1e202a; 41 | --third-background: #2b2e3b; 42 | --border: #0e1014; 43 | --text: #fff; 44 | --anchor: #8be9fd; 45 | --anchor-focus: #8be9fd; 46 | --input-focus: #8be9fd; 47 | --strong: #bd93f9; 48 | --hint: #7A7C85; 49 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 50 | 51 | --keyword: #ff79c6; 52 | --identifier: #f8f8f2; 53 | --comment: #6272a4; 54 | --operator: #ff79c6; 55 | --punctuation: #f8f8f2; 56 | --other: #f8f8f2; 57 | --escapeSequence: #bd93f9; 58 | --number: #bd93f9; 59 | --literal: #f1fa8c; 60 | --raw-data: #8be9fd; 61 | } 62 | 63 | .theme-switch-wrapper { 64 | display: flex; 65 | align-items: center; 66 | 67 | em { 68 | margin-left: 10px; 69 | font-size: 1rem; 70 | } 71 | } 72 | .theme-switch { 73 | display: inline-block; 74 | height: 22px; 75 | position: relative; 76 | width: 50px; 77 | } 78 | 79 | .theme-switch input { 80 | display: none; 81 | } 82 | 83 | .slider { 84 | background-color: #ccc; 85 | bottom: 0; 86 | cursor: pointer; 87 | left: 0; 88 | position: absolute; 89 | right: 0; 90 | top: 0; 91 | transition: .4s; 92 | } 93 | 94 | .slider:before { 95 | background-color: #fff; 96 | bottom: 4px; 97 | content: ""; 98 | height: 13px; 99 | left: 4px; 100 | position: absolute; 101 | transition: .4s; 102 | width: 13px; 103 | } 104 | 105 | input:checked + .slider { 106 | background-color: #66bb6a; 107 | } 108 | 109 | input:checked + .slider:before { 110 | transform: translateX(26px); 111 | } 112 | 113 | .slider.round { 114 | border-radius: 17px; 115 | } 116 | 117 | .slider.round:before { 118 | border-radius: 50%; 119 | } 120 | 121 | html { 122 | font-size: 100%; 123 | -webkit-text-size-adjust: 100%; 124 | -ms-text-size-adjust: 100%; } 125 | 126 | body { 127 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 128 | font-weight: 400; 129 | font-size: 1.125em; 130 | line-height: 1.5; 131 | color: var(--text); 132 | background-color: var(--primary-background); } 133 | 134 | /* Skeleton grid */ 135 | .container { 136 | position: relative; 137 | width: 100%; 138 | max-width: 1050px; 139 | margin: 0 auto; 140 | padding: 0; 141 | box-sizing: border-box; } 142 | 143 | .column, 144 | .columns { 145 | width: 100%; 146 | float: left; 147 | box-sizing: border-box; 148 | margin-left: 1%; 149 | } 150 | 151 | .column:first-child, 152 | .columns:first-child { 153 | margin-left: 0; } 154 | 155 | .three.columns { 156 | width: 19%; } 157 | 158 | .nine.columns { 159 | width: 80.0%; } 160 | 161 | .twelve.columns { 162 | width: 100%; 163 | margin-left: 0; } 164 | 165 | @media screen and (max-width: 860px) { 166 | .three.columns { 167 | display: none; 168 | } 169 | .nine.columns { 170 | width: 98.0%; 171 | } 172 | body { 173 | font-size: 1em; 174 | line-height: 1.35; 175 | } 176 | } 177 | 178 | cite { 179 | font-style: italic !important; } 180 | 181 | 182 | /* Nim search input */ 183 | div#searchInputDiv { 184 | margin-bottom: 1em; 185 | } 186 | input#searchInput { 187 | width: 80%; 188 | } 189 | 190 | /* 191 | * Some custom formatting for input forms. 192 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 193 | */ 194 | input { 195 | -moz-appearance: none; 196 | background-color: var(--secondary-background); 197 | color: var(--text); 198 | border: 1px solid var(--border); 199 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 200 | font-size: 0.9em; 201 | padding: 6px; 202 | } 203 | 204 | input:focus { 205 | border: 1px solid var(--input-focus); 206 | box-shadow: 0 0 3px var(--input-focus); 207 | } 208 | 209 | select { 210 | -moz-appearance: none; 211 | background-color: var(--secondary-background); 212 | color: var(--text); 213 | border: 1px solid var(--border); 214 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 215 | font-size: 0.9em; 216 | padding: 6px; 217 | } 218 | 219 | select:focus { 220 | border: 1px solid var(--input-focus); 221 | box-shadow: 0 0 3px var(--input-focus); 222 | } 223 | 224 | /* Docgen styles */ 225 | /* Links */ 226 | a { 227 | color: var(--anchor); 228 | text-decoration: none; 229 | } 230 | 231 | a span.Identifier { 232 | text-decoration: underline; 233 | text-decoration-color: #aab; 234 | } 235 | 236 | a.reference-toplevel { 237 | font-weight: bold; 238 | } 239 | 240 | a.toc-backref { 241 | text-decoration: none; 242 | color: var(--text); } 243 | 244 | a.link-seesrc { 245 | color: #607c9f; 246 | font-size: 0.9em; 247 | font-style: italic; } 248 | 249 | a:hover, 250 | a:focus { 251 | color: var(--anchor-focus); 252 | text-decoration: underline; } 253 | 254 | a:hover span.Identifier { 255 | color: var(--anchor); 256 | } 257 | 258 | 259 | sub, 260 | sup { 261 | position: relative; 262 | font-size: 75%; 263 | line-height: 0; 264 | vertical-align: baseline; } 265 | 266 | sup { 267 | top: -0.5em; } 268 | 269 | sub { 270 | bottom: -0.25em; } 271 | 272 | img { 273 | width: auto; 274 | height: auto; 275 | max-width: 100%; 276 | vertical-align: middle; 277 | border: 0; 278 | -ms-interpolation-mode: bicubic; } 279 | 280 | @media print { 281 | * { 282 | color: black !important; 283 | text-shadow: none !important; 284 | background: transparent !important; 285 | box-shadow: none !important; } 286 | 287 | a, 288 | a:visited { 289 | text-decoration: underline; } 290 | 291 | a[href]:after { 292 | content: " (" attr(href) ")"; } 293 | 294 | abbr[title]:after { 295 | content: " (" attr(title) ")"; } 296 | 297 | .ir a:after, 298 | a[href^="javascript:"]:after, 299 | a[href^="#"]:after { 300 | content: ""; } 301 | 302 | pre, 303 | blockquote { 304 | border: 1px solid #999; 305 | page-break-inside: avoid; } 306 | 307 | thead { 308 | display: table-header-group; } 309 | 310 | tr, 311 | img { 312 | page-break-inside: avoid; } 313 | 314 | img { 315 | max-width: 100% !important; } 316 | 317 | @page { 318 | margin: 0.5cm; } 319 | 320 | h1 { 321 | page-break-before: always; } 322 | 323 | h1.title { 324 | page-break-before: avoid; } 325 | 326 | p, 327 | h2, 328 | h3 { 329 | orphans: 3; 330 | widows: 3; } 331 | 332 | h2, 333 | h3 { 334 | page-break-after: avoid; } 335 | } 336 | 337 | 338 | p { 339 | margin-top: 0.5em; 340 | margin-bottom: 0.5em; 341 | } 342 | 343 | small { 344 | font-size: 85%; } 345 | 346 | strong { 347 | font-weight: 600; 348 | font-size: 0.95em; 349 | color: var(--strong); 350 | } 351 | 352 | em { 353 | font-style: italic; } 354 | 355 | h1 { 356 | font-size: 1.8em; 357 | font-weight: 400; 358 | padding-bottom: .25em; 359 | border-bottom: 6px solid var(--third-background); 360 | margin-top: 2.5em; 361 | margin-bottom: 1em; 362 | line-height: 1.2em; } 363 | 364 | h1.title { 365 | padding-bottom: 1em; 366 | border-bottom: 0px; 367 | font-size: 2.5em; 368 | text-align: center; 369 | font-weight: 900; 370 | margin-top: 0.75em; 371 | margin-bottom: 0em; 372 | } 373 | 374 | h2 { 375 | font-size: 1.3em; 376 | margin-top: 2em; } 377 | 378 | h2.subtitle { 379 | text-align: center; } 380 | 381 | h3 { 382 | font-size: 1.125em; 383 | font-style: italic; 384 | margin-top: 1.5em; } 385 | 386 | h4 { 387 | font-size: 1.125em; 388 | margin-top: 1em; } 389 | 390 | h5 { 391 | font-size: 1.125em; 392 | margin-top: 0.75em; } 393 | 394 | h6 { 395 | font-size: 1.1em; } 396 | 397 | 398 | ul, 399 | ol { 400 | padding: 0; 401 | margin-top: 0.5em; 402 | margin-left: 0.75em; } 403 | 404 | ul ul, 405 | ul ol, 406 | ol ol, 407 | ol ul { 408 | margin-bottom: 0; 409 | margin-left: 1.25em; } 410 | 411 | li { 412 | list-style-type: circle; 413 | } 414 | 415 | ul.simple-boot li { 416 | list-style-type: none; 417 | margin-left: 0em; 418 | margin-bottom: 0.5em; 419 | } 420 | 421 | ol.simple > li, ul.simple > li { 422 | margin-bottom: 0.25em; 423 | margin-left: 0.4em } 424 | 425 | ul.simple.simple-toc > li { 426 | margin-top: 1em; 427 | } 428 | 429 | ul.simple-toc { 430 | list-style: none; 431 | font-size: 0.9em; 432 | margin-left: -0.3em; 433 | margin-top: 1em; } 434 | 435 | ul.simple-toc > li { 436 | list-style-type: none; 437 | } 438 | 439 | ul.simple-toc-section { 440 | list-style-type: circle; 441 | margin-left: 1em; 442 | color: #6c9aae; } 443 | 444 | 445 | ol.arabic { 446 | list-style: decimal; } 447 | 448 | ol.loweralpha { 449 | list-style: lower-alpha; } 450 | 451 | ol.upperalpha { 452 | list-style: upper-alpha; } 453 | 454 | ol.lowerroman { 455 | list-style: lower-roman; } 456 | 457 | ol.upperroman { 458 | list-style: upper-roman; } 459 | 460 | ul.auto-toc { 461 | list-style-type: none; } 462 | 463 | 464 | dl { 465 | margin-bottom: 1.5em; } 466 | 467 | dt { 468 | margin-bottom: -0.5em; 469 | margin-left: 0.0em; } 470 | 471 | dd { 472 | margin-left: 2.0em; 473 | margin-bottom: 3.0em; 474 | margin-top: 0.5em; } 475 | 476 | 477 | hr { 478 | margin: 2em 0; 479 | border: 0; 480 | border-top: 1px solid #aaa; } 481 | 482 | blockquote { 483 | font-size: 0.9em; 484 | font-style: italic; 485 | padding-left: 0.5em; 486 | margin-left: 0; 487 | border-left: 5px solid #bbc; 488 | } 489 | 490 | .pre { 491 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 492 | font-weight: 500; 493 | font-size: 0.85em; 494 | color: var(--text); 495 | background-color: var(--third-background); 496 | padding-left: 3px; 497 | padding-right: 3px; 498 | border-radius: 4px; 499 | } 500 | 501 | pre { 502 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 503 | color: var(--text); 504 | font-weight: 500; 505 | display: inline-block; 506 | box-sizing: border-box; 507 | min-width: 100%; 508 | padding: 0.5em; 509 | margin-top: 0.5em; 510 | margin-bottom: 0.5em; 511 | font-size: 0.85em; 512 | white-space: pre !important; 513 | overflow-y: hidden; 514 | overflow-x: visible; 515 | background-color: var(--secondary-background); 516 | border: 1px solid var(--border); 517 | -webkit-border-radius: 6px; 518 | -moz-border-radius: 6px; 519 | border-radius: 6px; } 520 | 521 | .pre-scrollable { 522 | max-height: 340px; 523 | overflow-y: scroll; } 524 | 525 | 526 | /* Nim line-numbered tables */ 527 | .line-nums-table { 528 | width: 100%; 529 | table-layout: fixed; } 530 | 531 | table.line-nums-table { 532 | border-radius: 4px; 533 | border: 1px solid #cccccc; 534 | background-color: ghostwhite; 535 | border-collapse: separate; 536 | margin-top: 15px; 537 | margin-bottom: 25px; } 538 | 539 | .line-nums-table tbody { 540 | border: none; } 541 | 542 | .line-nums-table td pre { 543 | border: none; 544 | background-color: transparent; } 545 | 546 | .line-nums-table td.blob-line-nums { 547 | width: 28px; } 548 | 549 | .line-nums-table td.blob-line-nums pre { 550 | color: #b0b0b0; 551 | -webkit-filter: opacity(75%); 552 | text-align: right; 553 | border-color: transparent; 554 | background-color: transparent; 555 | padding-left: 0px; 556 | margin-left: 0px; 557 | padding-right: 0px; 558 | margin-right: 0px; } 559 | 560 | 561 | table { 562 | max-width: 100%; 563 | background-color: transparent; 564 | margin-top: 0.5em; 565 | margin-bottom: 1.5em; 566 | border-collapse: collapse; 567 | border-color: var(--third-background); 568 | border-spacing: 0; 569 | font-size: 0.9em; 570 | } 571 | 572 | table th, table td { 573 | padding: 0px 0.5em 0px; 574 | border-color: var(--third-background); 575 | } 576 | 577 | table th { 578 | background-color: var(--third-background); 579 | border-color: var(--third-background); 580 | font-weight: bold; } 581 | 582 | table th.docinfo-name { 583 | background-color: transparent; 584 | } 585 | 586 | table tr:hover { 587 | background-color: var(--third-background); } 588 | 589 | 590 | /* rst2html default used to remove borders from tables and images */ 591 | .borderless, table.borderless td, table.borderless th { 592 | border: 0; } 593 | 594 | table.borderless td, table.borderless th { 595 | /* Override padding for "table.docutils td" with "! important". 596 | The right padding separates the table cells. */ 597 | padding: 0 0.5em 0 0 !important; } 598 | 599 | .first { 600 | /* Override more specific margin styles with "! important". */ 601 | margin-top: 0 !important; } 602 | 603 | .last, .with-subtitle { 604 | margin-bottom: 0 !important; } 605 | 606 | .hidden { 607 | display: none; } 608 | 609 | blockquote.epigraph { 610 | margin: 2em 5em; } 611 | 612 | dl.docutils dd { 613 | margin-bottom: 0.5em; } 614 | 615 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 616 | overflow: hidden; } 617 | 618 | 619 | div.figure { 620 | margin-left: 2em; 621 | margin-right: 2em; } 622 | 623 | div.footer, div.header { 624 | clear: both; 625 | text-align: center; 626 | color: #666; 627 | font-size: smaller; } 628 | 629 | div.footer { 630 | padding-top: 5em; 631 | } 632 | 633 | div.line-block { 634 | display: block; 635 | margin-top: 1em; 636 | margin-bottom: 1em; } 637 | 638 | div.line-block div.line-block { 639 | margin-top: 0; 640 | margin-bottom: 0; 641 | margin-left: 1.5em; } 642 | 643 | div.topic { 644 | margin: 2em; } 645 | 646 | div.search_results { 647 | background-color: antiquewhite; 648 | margin: 3em; 649 | padding: 1em; 650 | border: 1px solid #4d4d4d; 651 | } 652 | 653 | div#global-links ul { 654 | margin-left: 0; 655 | list-style-type: none; 656 | } 657 | 658 | div#global-links > simple-boot { 659 | margin-left: 3em; 660 | } 661 | 662 | hr.docutils { 663 | width: 75%; } 664 | 665 | img.align-left, .figure.align-left, object.align-left { 666 | clear: left; 667 | float: left; 668 | margin-right: 1em; } 669 | 670 | img.align-right, .figure.align-right, object.align-right { 671 | clear: right; 672 | float: right; 673 | margin-left: 1em; } 674 | 675 | img.align-center, .figure.align-center, object.align-center { 676 | display: block; 677 | margin-left: auto; 678 | margin-right: auto; } 679 | 680 | .align-left { 681 | text-align: left; } 682 | 683 | .align-center { 684 | clear: both; 685 | text-align: center; } 686 | 687 | .align-right { 688 | text-align: right; } 689 | 690 | /* reset inner alignment in figures */ 691 | div.align-right { 692 | text-align: inherit; } 693 | 694 | p.attribution { 695 | text-align: right; 696 | margin-left: 50%; } 697 | 698 | p.caption { 699 | font-style: italic; } 700 | 701 | p.credits { 702 | font-style: italic; 703 | font-size: smaller; } 704 | 705 | p.label { 706 | white-space: nowrap; } 707 | 708 | p.rubric { 709 | font-weight: bold; 710 | font-size: larger; 711 | color: maroon; 712 | text-align: center; } 713 | 714 | p.topic-title { 715 | font-weight: bold; } 716 | 717 | pre.address { 718 | margin-bottom: 0; 719 | margin-top: 0; 720 | font: inherit; } 721 | 722 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 723 | margin-left: 2em; 724 | margin-right: 2em; } 725 | 726 | pre.code .ln { 727 | color: grey; } 728 | 729 | /* line numbers */ 730 | pre.code, code { 731 | background-color: #eeeeee; } 732 | 733 | pre.code .comment, code .comment { 734 | color: #5c6576; } 735 | 736 | pre.code .keyword, code .keyword { 737 | color: #3B0D06; 738 | font-weight: bold; } 739 | 740 | pre.code .literal.string, code .literal.string { 741 | color: #0c5404; } 742 | 743 | pre.code .name.builtin, code .name.builtin { 744 | color: #352b84; } 745 | 746 | pre.code .deleted, code .deleted { 747 | background-color: #DEB0A1; } 748 | 749 | pre.code .inserted, code .inserted { 750 | background-color: #A3D289; } 751 | 752 | span.classifier { 753 | font-style: oblique; } 754 | 755 | span.classifier-delimiter { 756 | font-weight: bold; } 757 | 758 | span.option { 759 | white-space: nowrap; } 760 | 761 | span.problematic { 762 | color: #b30000; } 763 | 764 | span.section-subtitle { 765 | /* font-size relative to parent (h1..h6 element) */ 766 | font-size: 80%; } 767 | 768 | span.DecNumber { 769 | color: var(--number); } 770 | 771 | span.BinNumber { 772 | color: var(--number); } 773 | 774 | span.HexNumber { 775 | color: var(--number); } 776 | 777 | span.OctNumber { 778 | color: var(--number); } 779 | 780 | span.FloatNumber { 781 | color: var(--number); } 782 | 783 | span.Identifier { 784 | color: var(--identifier); } 785 | 786 | span.Keyword { 787 | font-weight: 600; 788 | color: var(--keyword); } 789 | 790 | span.StringLit { 791 | color: var(--literal); } 792 | 793 | span.LongStringLit { 794 | color: var(--literal); } 795 | 796 | span.CharLit { 797 | color: var(--literal); } 798 | 799 | span.EscapeSequence { 800 | color: var(--escapeSequence); } 801 | 802 | span.Operator { 803 | color: var(--operator); } 804 | 805 | span.Punctuation { 806 | color: var(--punctuation); } 807 | 808 | span.Comment, span.LongComment { 809 | font-style: italic; 810 | font-weight: 400; 811 | color: var(--comment); } 812 | 813 | span.RegularExpression { 814 | color: darkviolet; } 815 | 816 | span.TagStart { 817 | color: darkviolet; } 818 | 819 | span.TagEnd { 820 | color: darkviolet; } 821 | 822 | span.Key { 823 | color: #252dbe; } 824 | 825 | span.Value { 826 | color: #252dbe; } 827 | 828 | span.RawData { 829 | color: var(--raw-data); } 830 | 831 | span.Assembler { 832 | color: #252dbe; } 833 | 834 | span.Preprocessor { 835 | color: #252dbe; } 836 | 837 | span.Directive { 838 | color: #252dbe; } 839 | 840 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 841 | span.Other { 842 | color: var(--other); } 843 | 844 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 845 | dt pre > span.Identifier, dt pre > span.Operator { 846 | color: var(--identifier); 847 | font-weight: 700; } 848 | 849 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 850 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 851 | color: var(--identifier); 852 | font-weight: inherit; } 853 | 854 | /* Nim sprite for the footer (taken from main page favicon) */ 855 | .nim-sprite { 856 | display: inline-block; 857 | width: 51px; 858 | height: 14px; 859 | background-position: 0 0; 860 | background-size: 51px 14px; 861 | -webkit-filter: opacity(50%); 862 | background-repeat: no-repeat; 863 | background-image: var(--nim-sprite-base64); 864 | margin-bottom: 5px; } 865 | 866 | span.pragmadots { 867 | /* Position: relative frees us up to make the dots 868 | look really nice without fucking up the layout and 869 | causing bulging in the parent container */ 870 | position: relative; 871 | /* 1px down looks slightly nicer */ 872 | top: 1px; 873 | padding: 2px; 874 | background-color: var(--third-background); 875 | border-radius: 4px; 876 | margin: 0 2px; 877 | cursor: pointer; 878 | font-size: 0.8em; 879 | } 880 | 881 | span.pragmadots:hover { 882 | background-color: var(--hint); 883 | } 884 | span.pragmawrap { 885 | display: none; 886 | } 887 | 888 | span.attachedType { 889 | display: none; 890 | visibility: hidden; 891 | } -------------------------------------------------------------------------------- /docs/timerpool.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | timerpool 21 | 22 | 23 | 24 | 25 | 69 | 70 | 71 | 72 |
73 |
74 |

timerpool

75 |
76 |
77 |
78 | 82 |     Dark Mode 83 |
84 | 88 |
89 | Search: 91 |
92 |
93 | Group by: 94 | 98 |
99 | 160 | 161 |
162 |
163 |
164 | 165 |

simple timerpool implementation for uncritical event-purposes. The "tick" is an abstract value and depends on the selected timebase and the environment

166 |

Its useful if you need wakeup-timers for protocol implementations or you like to calculate/interpolate something for a given timeslot

167 |

For each TimerPool object only one tickthread is spawned which handles the message-queue and the lifecycle of the TimerHandle. The maximum amount of timers is only constrained by memory and the given timebase.

168 |

The allocation of a new TimerHandle always block but is threadsafe. The maximum blocking-time relates directly to your given timebase of the pool

169 |

There is a blocking and a nonblocking API on the TimerHandles which can be used simulataneously from different threads at once. All actions on the TimerHandles are completely threadsafe and the ptrs itself can be shared between threads.

170 |

the following example demonstrates the basic use. For detailed api use and for multithreading examples use the tests as a starter.

171 |
import timerpool
172 | 
173 | let
174 |   tpRef = timerpool.newTimerPool(10.int) # timerpool with 10ms timebase
175 |   timerhdl = allocTimer(tpRef)
176 | 
177 | timerhdl.setAlarmCounter(5)  # set expiration to 50ms (timebase * 5)
178 | 
179 | while timerhdl.getAlarmCounter() > 0: # you can poll it
180 |   discard
181 | 
182 | timerhdl.waitForAlarm()     # or sleep till timer expired
183 | timerhdl.deallocTimer()     # pushes the timer back to pool
184 | tpRef.shutdownTimerPool()   # shutdown the pool and blocks till all
185 |                             # timers are expired

186 |
187 |

Types

188 |
189 | 190 |
TimerHandlePtr = ptr TimerHandle
191 |
192 | 193 | pointer type to the timerpoolhandle. 194 | 195 |
196 | 197 |
TPError = object of Exception
198 |   
199 |
200 | 201 | generic exception 202 | 203 |
204 | 205 |
TimerPoolPtr = ptr TimerPool
206 |
207 | 208 | used to share among threads 209 | 210 |
211 | 212 |
TimerPoolRef = ref TimerPool
213 |
214 | 215 | 216 | 217 |
218 | 219 |
Tickval = range[1 .. int.high]
220 |
221 | 222 | 223 | 224 |
225 | 226 |
MinTimerval = range[1 .. int.high]
227 |
228 | 229 | integer type used to initialise the timerpool and to set the timeout of the timer 230 | 231 |
232 | 233 |
PoolStats = tuple[runningCount: int, freedCount: int, inactiveCount: int]
234 |
235 | 236 | container type returned by waitForGetStats. the sum of runningCount,freedCount and inactiveCount is the total amount of timerhandles within the pool 237 | 238 |
239 | 240 |
241 |
242 |

Procs

243 |
244 | 245 |
proc initThreadContext(tpptr: TimerPoolPtr): void {...}{.raises: [TPError], tags: [].}
246 |
247 | 248 |

to be called explicit if the pool-accessing thread is not the owner of the timerpool (initialises threadvar globs)

249 |

raises a TPError if called within the spawning thread

250 | 251 | 252 |
253 | 254 |
proc newTimerPool(tbase_ms: Tickval = 100; minFreedTimers: MinTimerval = 5): ref TimerPool {...}{.
255 |     raises: [], tags: [].}
256 |
257 | 258 | creator proc. The tickval is of milliseconds and the default timebase is 100 milliseconds the default of the mintimers parameter is 5 (shrink_pool leave this minimum amount of freed timers within the pool) 259 | 260 |
261 | 262 |
proc deinitThreadContext(tpptr: TimerPoolPtr): void {...}{.raises: [TPError], tags: [].}
263 |
264 | 265 |

call this proc if the pool-accessing thread should be detached from the timerpool (cleanup threadvar globs)

266 |

call this proc only if the current thread is not owner of the timerpool. if not a TPError is raised

267 | 268 | 269 |
270 | 271 |
proc allocTimer(tpptr: TimerPoolPtr): TimerHandlePtr {...}{.raises: [TPError], tags: [].}
272 |
273 | 274 |

returns a timerhandle. the timer is always of type:oneshot but could also act as a continous one. in this case the caller needs to reset the alarm to the needed value. This threadsafe call blocks till the request was handled by the pool-tick-thread

275 |

before calling (if the pool was not spawned by the calling thread) initThreadContext() should be called

276 |

raises TPError if the pointer parameter is nil and/or the threadContext was not initialised with initThreadContext

277 | 278 | 279 |
280 | 281 |
proc allocTimer(tpptr: TimerPoolRef): TimerHandlePtr {...}{.inline, raises: [TPError],
282 |     tags: [].}
283 |
284 | 285 | 286 | 287 |
288 | 289 |
proc deallocTimer(timerhdl: TimerHandlePtr): void {...}{.raises: [TPError], tags: [].}
290 |
291 | 292 |

the timer handle is pushed back to the pool. once freed it is not handled by the timerscan any more and its recycled for later use

293 |

this proc could be called from multiple threads simultaneously. if one ore more threads are waiting on the timers signal all threads gets informed. This call is part of the nonblocking api

294 |

raises TPError if the pointer parameter is nil

295 | 296 | 297 |
298 | 299 |
proc setAlarmCounter(timerhdl: TimerHandlePtr; value: Tickval): void {...}{.
300 |     raises: [TPError], tags: [].}
301 |
302 | 303 |

sets the timers countdown alarm-value to the given one. reset the counter after it´s fired to obtain a continous timer

304 |

this call is threadsafe and part of the nonblocking-api

305 |

raises TPError if the pointer parameter is nil or the timer is freed

306 | 307 | 308 |
309 | 310 |
proc getAlarmCounter(timerhdl: TimerHandlePtr): int {...}{.raises: [TPError], tags: [].}
311 |
312 | 313 |

returns the current value of the alarmcounter could be used for a polling-style-waiting_for_timer_fired

314 |

this call is threadsafe and part of the nonblocking-api

315 |

raises TPError if the pointer parameter is nil or the timer already freed

316 | 317 | 318 |
319 | 320 |
proc waitForAlarm(timerhdl: TimerHandlePtr): void {...}{.raises: [TPError], tags: [].}
321 |
322 | 323 |

blocking wait till the alarmcounter is decremented to 0

324 |

threadsafe impl and could be called by multiple threads simultaniously

325 |

raises TPError if the pointer parameter is nil or the timer already freed

326 | 327 | 328 |
329 | 330 |
proc waitForGetStats(tpptr: TimerPoolPtr): PoolStats {...}{.raises: [TPError], tags: [].}
331 |
332 | 333 |

fetches some pool statistics for debugging purposes

334 |

raises TPError if the pointer parameter is nil or the threadContext was not initialized with initThreadContext

335 | 336 | 337 |
338 | 339 |
proc shrinkTimerPool(tpptr: TimerPoolPtr) {...}{.raises: [TPError], tags: [].}
340 |
341 | 342 |

shrinks the pool of freed Timers. the given minFreedTimers value at pool construction specifies the lower watermark

343 |

this is a nonblocking call. raises TPError if the pointer parameter is nil and/or the threadContext was not initialised with initThreadContext (only needed if the pool was not spawned by the caller)

344 | 345 | 346 |
347 | 348 |
349 |
350 |

Templates

351 |
352 | 353 |
template checkForNil(timerhdl: TimerHandlePtr; callingProc: string = ""): void
354 |
355 | 356 | checks if the timerhdl is nil. if so a TPError is raised 357 | 358 |
359 | 360 |
template poolRef2Ptr(stpp: TimerPoolRef): TimerPoolPtr
361 |
362 | 363 | convenience template to get the TimerPoolPtr from the ref 364 | 365 |
366 | 367 |
368 | 369 |
370 |
371 | 372 |
373 | 378 |
379 |
380 |
381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /tests/humanecho.nim: -------------------------------------------------------------------------------- 1 | import terminal,strutils,random,os,threadpool 2 | import ../timerpool 3 | 4 | # funstuff: human type simulator 5 | # simple showcase to demonstrate the timerpool usage 6 | 7 | # we split the string into words and calculate random 8 | # waits on each char 9 | # each word is written to the console in different threads 10 | # (for each word one thread and timer) 11 | # at different timeslots (this lead to simulated type errors) 12 | 13 | type 14 | WordIndex = tuple[startidx:int,length:int] 15 | # each word inside the string is indexed with start and length 16 | 17 | proc getWordIdx(p : string) : seq[WordIndex] = 18 | result = newSeq[WordIndex](0) 19 | var subresult : WordIndex = (0,p.high) 20 | 21 | for i in 0 .. p.high: 22 | if isSpaceAscii(p[i]) or i == p.high: 23 | # iterate over the string and check word boundary or end of string 24 | subresult.length = i 25 | result.add(subresult) 26 | subresult = (i+1,p.high) 27 | 28 | type 29 | StringPtr = ptr string 30 | WordChunk = tuple[payload : StringPtr,idxrange : WordIndex, 31 | timer: TimerHandlePtr] 32 | 33 | proc outputWord(dest : File, output:WordChunk, 34 | rwaits:seq[int], timeslot : int = 0) : void {.thread.} = 35 | ## output worker method. 36 | var ctr = 0 37 | if timeslot > 0: 38 | output.timer.setAlarmCounter(timeslot) 39 | output.timer.waitForAlarm # wait on our timeslot 40 | for i in output.idxrange.startidx .. output.idxrange.length: 41 | output.timer.setAlarmCounter(rwaits[ctr]) 42 | stdout.write(output.payload[i]) 43 | output.timer.waitForAlarm 44 | inc(ctr) 45 | output.timer.deallocTimer() 46 | 47 | proc absTypingTime(val:seq[int]) : int = 48 | ## get sum of rand (absolute typing time per word) 49 | result = 0 50 | for x in val: 51 | result = result + x 52 | 53 | proc generateAbsRandomWaitsPerChar(val:string, metadata: WordIndex) : seq[int] = 54 | result = newSeq[int](0) 55 | for idx in metadata.startidx..metadata.length: 56 | result.add(rand(range[10.int..20.int])) # TODO parameterize the behaviour 57 | 58 | proc echoTyped*(dest : File, payload : string) = 59 | ## funny string output with possible errors 60 | let tp = newTimerPool(10) 61 | var 62 | pl : string = payload 63 | words :seq[WordIndex] = getWordIdx(pl) 64 | sptr : StringPtr = pl.addr 65 | offset = 0.int 66 | 67 | for word in words: 68 | let waitsperchar = generateAbsRandomWaitsPerChar(pl,word) 69 | var chunk = (sptr,word,tp.allocTimer) 70 | spawn outputWord(dest,chunk,waitsperchar,offset) 71 | offset = offset + (absTypingTime(waitsperchar) - rand(range[1.int..25.int])) 72 | # simulate type error by substracting something from the timeslot-offset 73 | 74 | sync() 75 | tp.shutdownTimerPool() 76 | 77 | when isMainModule: 78 | stdout.echoTyped("Hello Nim....") 79 | stdout.echoTyped(" follow the white rabbit ") 80 | stdout.echoTyped("and dont forget to take the red pill .. :-)") 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /tests/timerpool_test.nim: -------------------------------------------------------------------------------- 1 | import ../timerpool 2 | import times,threadpool,os 3 | import unittest 4 | 5 | suite "general_tests": 6 | setup: 7 | var 8 | stpRef = timerpool.newTimerPool(10.int,1.int) 9 | timerhdls = newSeq[TimerHandlePtr](10) 10 | stpPtr = poolRef2Ptr(stpRef) 11 | 12 | for i in timerhdls.low .. timerhdls.high: 13 | timerhdls[i] = poolRef2Ptr(stpRef).allocTimer() 14 | 15 | teardown: 16 | stpRef.shutdownTimerPool() 17 | 18 | # test the timerstates 19 | test "test_timerstate": 20 | 21 | # set all timers fire after 5 ticks 22 | for i in timerhdls.low .. timerhdls.high: 23 | timerhdls[i].setAlarmCounter(5.int) # run for about 50ms (timerbase*5) 24 | var statsBefore : PoolStats = stpPtr.waitForGetStats 25 | sleep(70) # wait till timer fired 26 | var statsAfter = stpPtr.waitForGetStats 27 | for i in timerhdls: 28 | i.deallocTimer() 29 | var statsFinal = stpPtr.waitForGetStats 30 | check: 31 | statsBefore.runningCount == 10 32 | statsBefore.freedCount == 0 33 | statsBefore.inactiveCount == 0 34 | statsAfter.runningCount == 0 35 | statsAfter.freedCount == 0 36 | statsAfter.inactiveCount == 10 37 | statsFinal.runningCount == 0 38 | statsFinal.freedCount == 10 39 | statsFinal.inactiveCount == 0 40 | 41 | test "shrinkPool": 42 | for i in timerhdls: 43 | i.deallocTimer() 44 | var statsAfterDealloc = stpPtr.waitForGetStats 45 | stpPtr.shrinkTimerPool 46 | sleep(500) 47 | var statsAfterShrink = stpPtr.waitForGetStats 48 | 49 | check: 50 | statsAfterDealloc.runningCount == 0 51 | statsAfterDealloc.freedCount == 10 52 | statsAfterDealloc.inactiveCount == 0 53 | statsAfterShrink.runningCount == 0 54 | statsAfterShrink.freedCount == 1 55 | statsAfterShrink.inactiveCount == 0 56 | 57 | 58 | test "timerExceptions": 59 | for i in timerhdls.low .. timerhdls.high: 60 | timerhdls[i].deallocTimer() 61 | # call on freed timer should thow an exception 62 | expect(timerpool.TPError): 63 | timerhdls[timerhdls.low].setAlarmCounter(50) 64 | expect(timerpool.TPError): 65 | timerhdls[timerhdls.low].deallocTimer() 66 | expect(timerpool.TPError): 67 | discard timerhdls[timerhdls.low].getAlarmCounter() 68 | expect(timerpool.TPError): 69 | timerhdls[timerhdls.low].waitForAlarm() 70 | 71 | suite "test_threading": 72 | setup: 73 | var 74 | stpRef= timerpool.newTimerPool(10.int) 75 | timerhdls = newSeq[TimerHandlePtr](5) 76 | for i in timerhdls.low .. timerhdls.high: 77 | timerhdls[i] = (poolRef2Ptr(stpRef)).allocTimer() 78 | 79 | teardown: 80 | stpRef.shutdownTimerPool() 81 | 82 | test "one_timer_200_childthreads": 83 | # worker proc per thread 84 | proc dosomething(timerhdl :TimerHandlePtr) : int = 85 | result = 1 86 | timerhdl.waitForAlarm() 87 | 88 | var presults = newSeq[FlowVar[int]](200) 89 | timerhdls[0].setAlarmCounter(10) # 100ms (timerbase*10) 90 | 91 | for i in presults.low..presults.high: 92 | presults[i] = spawn dosomething(timerhdls[0]) 93 | discard stpRef.poolRef2Ptr.waitForGetStats 94 | timerhdls[0].waitForAlarm() 95 | # every thread is also waiting on it. when finished the results 96 | # are present 97 | var tresult : int = 0 98 | for i in presults.low..presults.high: 99 | tresult = tresult + ^presults[i] 100 | 101 | check: 102 | tresult == 200 103 | 104 | test "early_wakeup": 105 | # multiple threads are waiting on a timer 106 | # we dealloc the timer before it's done 107 | # all threads should wakeup immediately 108 | proc dosomething(timerhdl :TimerHandlePtr) : int = 109 | result = 1 110 | timerhdl.waitForAlarm() 111 | var presults = newSeq[FlowVar[int]](250) 112 | 113 | timerhdls[0].setAlarmCounter(900) # 9000ms 114 | timerhdls[1].setAlarmCounter(50) 115 | 116 | var ctime = cpuTime() 117 | 118 | for i in presults.low..presults.high: 119 | presults[i] = spawn dosomething(timerhdls[0]) 120 | timerhdls[0].deallocTimer() # dealloc before done 121 | # every thread is also waiting on it. if finished the results 122 | # are present 123 | var tresult : int = 0 124 | for i in presults.low..presults.high: 125 | tresult = tresult + ^presults[i] 126 | ctime = cpuTime() - ctime 127 | 128 | check: 129 | tresult == 250 130 | ctime < 500 131 | 132 | test "multiple_threads_alloc": 133 | # multiple threads requesting a new timer from the pool 134 | proc dosomething(poolhdl :TimerPoolPtr) : int = 135 | var timer : TimerHandlePtr = nil 136 | try: 137 | initThreadContext(poolhdl) 138 | timer = poolhdl.allocTimer() 139 | timer.setAlarmCounter(2) 140 | # do something till timeout reached 141 | while timer.getAlarmCounter() > 0: 142 | result = result + 1 143 | except: 144 | echo getCurrentExceptionMsg() 145 | finally: 146 | timer.deallocTimer() 147 | deinitThreadContext(poolhdl) 148 | 149 | var presults = newSeq[FlowVar[int]](250) 150 | for i in presults.low..presults.high: 151 | presults[i] = spawn dosomething(poolRef2Ptr(stpRef)) 152 | 153 | var tresult : int = 0 154 | 155 | for i in presults.low..presults.high: 156 | tresult = tresult + ^presults[i] 157 | 158 | # snd run 159 | for i in presults.low..presults.high: 160 | presults[i] = spawn dosomething(poolRef2Ptr(stpRef)) 161 | 162 | var tresult2 : int = 0 163 | 164 | for i in presults.low..presults.high: 165 | tresult2 = tresult2 + ^presults[i] 166 | 167 | #thrd run 168 | 169 | for i in presults.low..presults.high: 170 | presults[i] = spawn dosomething(poolRef2Ptr(stpRef)) 171 | 172 | var tresult3 : int = 0 173 | 174 | for i in presults.low..presults.high: 175 | tresult3 = tresult3 + ^presults[i] 176 | -------------------------------------------------------------------------------- /timerpool.nim: -------------------------------------------------------------------------------- 1 | # simple timerpool implementation in Nim 2 | # Copyright (c) 2017 Michael Krauter 3 | # please see the LICENSE-file for details. 4 | 5 | import times, sequtils, deques, locks, os, concurrency/atomics 6 | 7 | ## simple timerpool implementation for uncritical event-purposes. 8 | ## The "tick" is an abstract value and depends 9 | ## on the selected timebase and the environment 10 | ## 11 | ## Its useful if you need wakeup-timers for protocol implementations or you like 12 | ## to calculate/interpolate something for a given timeslot 13 | ## 14 | ## For each TimerPool object only one tickthread is spawned which handles 15 | ## the message-queue and the lifecycle of the TimerHandle. 16 | ## The maximum amount of timers is only constrained by memory 17 | ## and the given timebase. 18 | ## 19 | ## The allocation of a new TimerHandle always block but is threadsafe. 20 | ## The maximum blocking-time relates directly to your given 21 | ## timebase of the pool 22 | ## 23 | ## There is a blocking and a nonblocking API on the TimerHandles 24 | ## which can be used simulataneously from different threads at once. 25 | ## All actions on the TimerHandles are completely threadsafe 26 | ## and the ptrs itself can be shared between threads. 27 | ## 28 | ## the following example demonstrates the basic use. 29 | ## For detailed api use and for multithreading examples 30 | ## use the tests as a starter. 31 | ## 32 | ## .. code-block:: nim 33 | ## import timerpool 34 | ## 35 | ## let 36 | ## tpRef = timerpool.newTimerPool(10.int) # timerpool with 10ms timebase 37 | ## timerhdl = allocTimer(tpRef) 38 | ## 39 | ## timerhdl.setAlarmCounter(5) # set expiration to 50ms (timebase * 5) 40 | ## 41 | ## while timerhdl.getAlarmCounter() > 0: # you can poll it 42 | ## discard 43 | ## 44 | ## timerhdl.waitForAlarm() # or sleep till timer expired 45 | ## timerhdl.deallocTimer() # pushes the timer back to pool 46 | ## tpRef.shutdownTimerPool() # shutdown the pool and blocks till all 47 | ## # timers are expired 48 | ## 49 | ## 50 | # TODO: test more envs - at the moment only tested 51 | # on windows10 (Intel N3540,x64) 52 | # 53 | # 54 | # some implementation hints: 55 | # the TimerHandles are maintained and owned by the tickthread 56 | # 57 | # instead of maintaining and handling multiple 58 | # messages per thread there is only one message/action per thread possible (PMsg). 59 | # The pointer to this object is stored within the thread-local var 60 | # "threadContext" and it's initialized by calling "initThreadContext". 61 | # By calling newTimerPool this proc is called implicitly. 62 | # Due to that (and to simplify the api) the allocation of a new timer 63 | # and retrieving some pool statistics is always blocking. The maximum 64 | # idle time is related to your timebase. 65 | # Once allocated, all actions on the timer itself could be blocking 66 | # or nonblocking dependend on your needs 67 | # 68 | # tested with gcc(x64) 69 | # Thread model: posix 70 | # gcc version 5.1.0 (tdm64-1) 71 | # 72 | # cl.exe 19.11.25507.1(x64) 73 | 74 | when not compileOption("threads"): 75 | {.error: "TimerPool requires --threads:on option.".} 76 | 77 | type 78 | TimerHandle = object 79 | # the timer is active if alarmctr > 0 and not freed 80 | alarmctr: Atomic[int] # countdown field 81 | waitLock: Lock # lock used for the blocking-style alarm api 82 | waitCond: Cond # condition associated to the waitLock 83 | isFreed: Atomic[bool] # true if the owner of the handle is the pool 84 | waitingOnLockCount: Atomic[int] 85 | # counts how many threads waiting on the lock. 86 | # needed that no signal is lost 87 | 88 | TimerHandleRef = ref TimerHandle # used by the tickthread 89 | 90 | TimerHandlePtr* = ptr TimerHandle 91 | ## pointer type to the timerpoolhandle. 92 | 93 | SomePtr = ptr object # ugly solution cause Thread needs a concrete type 94 | TPError* = object of Exception 95 | ## generic exception 96 | type 97 | PoolCmd = enum requestTimer, poolStats, killPool, shrinkPool, noOp 98 | PoolReply = enum success, abort 99 | # success is the default reply; abort is always answered if the 100 | # pool is about to shutdown 101 | 102 | # guard pragma not possible here because the lock and the fields 103 | # reside within different objects 104 | type 105 | PMsg = object # message which is passed to the tickthread 106 | cmd: PoolCmd 107 | reply: PoolReply 108 | allocTimerCompleteCond: Cond 109 | replyTimerHandlePtr: TimerHandlePtr 110 | poolStatsCompleteCond: Cond # allows waiting for the getStats 111 | statRunningTimers: int # alarmcounter > 0 112 | statInactiveTimers: int # alarmcounter == 0, prev fired 113 | statFreedTimers: int # hdl released back to pool 114 | 115 | PMsgPtr = ptr PMsg 116 | PMsgRef = ref PMsg 117 | # global var which needs to be initialized with initThreadContext 118 | # if we are not the owner of the object 119 | var threadContext {.threadvar.}: PMsgRef 120 | 121 | type 122 | # queue for emiting the pool commands to the workerthread 123 | # for low resource environments an array could be used instead 124 | CmdQueuePtr = ptr Deque[PmsgPtr] 125 | CmdQueue = Deque[PmsgPtr] 126 | ThreadArg = tuple[poolobjptr: SomePtr, minFreedTimers: int] 127 | 128 | type 129 | TimerPool = object 130 | timebase: int # the timebase of the tickthread 131 | tickthread: Thread[ThreadArg] 132 | # Lock for accessing the cmd-queue and check for poolShutdownDone 133 | poolReqLock: Lock 134 | cmdQueue {.guard: poolReqLock.}: CmdQueue 135 | poolShutdownDoneCond: Cond 136 | spawningThreadId: int 137 | 138 | type 139 | TimerPoolPtr* = ptr TimerPool 140 | ## used to share among threads 141 | TimerPoolRef* = ref TimerPool 142 | 143 | # timer_state templates 144 | template timerRunning(timerref: TimerHandleRef): bool = 145 | not atomics.load(timerref[].isFreed).bool and 146 | atomics.load(timerref[].alarmctr).int > 0 147 | 148 | template timerDone(timerref: TimerHandleRef): bool = 149 | not atomics.load(timerref[].isFreed).bool and 150 | atomics.load(timerref[].alarmctr).int == 0 151 | 152 | template timerFreed(timerref: TimerHandleRef): bool = 153 | atomics.load(timerref.isFreed).bool 154 | 155 | template threadWaiting(timerref: TimerHandleRef): bool = 156 | atomics.load(timerref.waitingOnLockCount).int > 0 157 | 158 | # api templates 159 | template checkForValidThreadContext(): void = 160 | if threadContext.isNil: 161 | raise newException( 162 | TPError, " please call initThreadContext() before using the API ") 163 | 164 | template checkForNil*(timerhdl: TimerHandlePtr, 165 | callingProc: string = ""): void = 166 | ## checks if the timerhdl is nil. if so a TPError is raised 167 | if timerhdl.isNil: 168 | raise newException(TPError, callingProc & ": timer_handle is nil ") 169 | 170 | template checkForNil(stpp: TimerPoolPtr, callingProc: string = ""): void = 171 | if stpp.isNil: 172 | raise newException(TPError, callingProc & ": TimerPoolPtr is nil ") 173 | 174 | template checkIfSpawningThread(tpptr: TimerPoolPtr) = 175 | if tpptr.spawningThreadId == getThreadId(): 176 | raise newException(TPError, " execution of this proc prohibited within the owning thread ") 177 | 178 | template poolRef2Ptr*(stpp: TimerPoolRef): TimerPoolPtr = 179 | ## convenience template to get the TimerPoolPtr from the ref 180 | (cast[TimerPoolPtr](stpp)) 181 | 182 | template msgRef2Ptr(pmsgref: PMsgRef): PMsgPtr = 183 | (cast[PMsgPtr](pmsgref)) 184 | 185 | template abortWhenTimerFreed(timerhdl: TimerHandlePtr, p: string) = 186 | if atomics.load(timerhdl.isFreed).bool: 187 | # TODO: provide better debug info which timer was freed 188 | # and from which source to trackdown nasty sharing errors 189 | raise newException(TPError, p & " timer already freed ") 190 | 191 | template waitOnTimerhdl(timerhdl: TimerHandlePtr) = 192 | # wait counter. each wait_condition is counted. this ensures 193 | # that the signaling side (the worker thread which calls "signal") 194 | # knows how many times "signal" must be called to wake up all waiting 195 | # threads properly (the Lock-api has no notify_all-style call at the moment) 196 | discard atomics.fetchAdd(timerhdl.waitingOnLockCount, 1) 197 | wait(timerhdl.waitCond, timerhdl.waitLock) 198 | discard atomics.fetchSub(timerhdl.waitingOnLockCount, 1) 199 | 200 | template waitOnStatsComplete(stpp: TimerPoolPtr, req: PMsgRef) = 201 | wait(req.poolStatsCompleteCond, stpp.poolReqLock) 202 | 203 | template validatePoolReply(rep: PMsgRef) = 204 | if rep.reply == PoolReply.abort: 205 | raise newException(TPError, " pool is about to shutdown - request aborted ") 206 | 207 | type 208 | ShutdownState = enum poolRunning, shutdownRequested, doShutdown 209 | # once shutdown recognised, the commandqueue isn´t processed anymore 210 | # but the workerloop still processes the running timers (shutdownRequested) 211 | # once all timers are fired, the state goes to doShutdown, all resources 212 | # are freed and the workerthread bails out 213 | 214 | proc findFreeTimer(sptr: seq[TimerHandleRef]): TimerHandleRef = 215 | # searches for an unused timerhdl (isFreed) 216 | # nil is returned if no unused timerhdl present 217 | result = nil 218 | 219 | for n in filter[TimerHandleRef](sptr, 220 | proc (x: TimerHandleRef): bool = 221 | if not x.isNil: 222 | result = cast[system.bool](timerFreed(x)) 223 | else: 224 | result = false): 225 | result = n 226 | break 227 | 228 | 229 | proc timerPoolWorkLoop(startupcontext: ThreadArg) {.thread.} = 230 | let 231 | sptr: TimerPoolPtr = cast[TimerPoolPtr](startupcontext.poolobjptr) 232 | mintimers: int = startupcontext.minFreedTimers 233 | var 234 | allTHandles: seq[TimerHandleRef] = newSeq[TimerHandleRef](0) 235 | runningTimersCount: int 236 | freedTimersCount: int 237 | inactiveTimersCount: int 238 | shutdownState: ShutdownState = ShutdownState.poolRunning 239 | currTime: float 240 | poolIdle: bool # true if all timers freed 241 | 242 | poolIdle = false 243 | 244 | while true: 245 | 246 | # measure the time we need for waiting on the lock and doing the work, 247 | # substract this from the given sleeping-time to get a smoothed timebase 248 | currTime = cpuTime() 249 | 250 | runningTimersCount = 0 251 | inactiveTimersCount = 0 252 | 253 | if not poolIdle: # perform pool scan 254 | freedTimersCount = 0 # preserve the last known value if poolIdle 255 | for i in allTHandles.low .. allTHandles.high: 256 | let timer = allTHandles[i] 257 | if not timer.isNil: 258 | if timerRunning(allTHandles[i]): 259 | discard atomics.fetchSub(allTHandles[i].alarmctr, 1) 260 | runningTimersCount = runningTimersCount + 1 261 | elif timerFreed(allTHandles[i]): 262 | freedTimersCount = freedTimersCount + 1 263 | else: 264 | inactiveTimersCount = inactiveTimersCount + 1 265 | 266 | if timerDone(allTHandles[i]) or timerFreed(allTHandles[i]): 267 | # we need also check for freed-state because the timer could 268 | # be freed while it's counting 269 | while threadWaiting(allTHandles[i]): 270 | signal(allTHandles[i].waitCond) 271 | # we call signal for each waiting thread 272 | 273 | poolIdle = (runningTimersCount + inactiveTimersCount) == 0 274 | # TODO: perform sleep if the pool stays, for given amount of cycles, idle 275 | # we need a new signal which must be sent every time when a new command 276 | # is put into the queue 277 | 278 | if shutdownState == ShutdownState.poolRunning: 279 | # read out the queue. for each run we consume the entire queue 280 | 281 | withLock(sptr.poolReqLock): 282 | # only ptr-type allowed to prevent the thread local gc 283 | # playing with it 284 | let cmdqueueptr: CmdQueuePtr = 285 | cast[CmdQueuePtr](sptr.cmdQueue.addr) 286 | 287 | while cmdqueueptr[].len > 0: 288 | let pmsgptr: PMsgPtr = cmdqueueptr[].popLast 289 | let activeCommand = pmsgptr.cmd 290 | 291 | case activeCommand 292 | 293 | of requestTimer: 294 | poolIdle = false 295 | var timerHandle = findFreeTimer(allTHandles) 296 | if timerHandle.isNil: 297 | # initialise new handle 298 | # as stated here by araq https://forum.nim-lang.org/t/104 299 | # allocShared is not needed (also see TimerPool ctor) 300 | # and the gc does the job for us 301 | timerhandle = cast[TimerHandleRef] 302 | (new TimerHandle) 303 | initLock(timerHandle.waitLock) 304 | initCond(timerHandle.waitCond) 305 | allTHandles.add(timerHandle) 306 | # recycled handle found 307 | atomics.store(timerHandle.alarmctr, 0.int) 308 | atomics.store(timerHandle.isFreed, false) 309 | atomics.store(timerHandle.waitingOnLockCount, 0.int) 310 | # init defaults 311 | 312 | pmsgptr.reply = PoolReply.success 313 | pmsgptr.replyTimerHandlePtr = cast[TimerHandlePtr] 314 | (timerHandle) 315 | signal(pmsgptr.allocTimerCompleteCond) 316 | # send response back to calling thread 317 | 318 | of poolStats: 319 | pmsgptr.statRunningTimers = runningTimersCount 320 | pmsgptr.statFreedTimers = freedTimersCount 321 | pmsgptr.statInactiveTimers = inactiveTimersCount 322 | signal(pmsgptr.poolStatsCompleteCond) 323 | 324 | of killPool: 325 | shutdownState = ShutdownState.shutdownRequested 326 | 327 | of shrinkPool: 328 | if freedTimersCount > minTimers: 329 | freedTimersCount = 0 330 | var 331 | newAllTHandles: seq[TimerHandleRef] = newSeq[TimerHandleRef]( 332 | runningTimersCount + 333 | inactiveTimersCount+minTimers) 334 | newIdx: int = 0 335 | recoveredCount: int = 0 336 | 337 | for hdl in allTHandles: 338 | if not hdl.isNil: 339 | if not atomics.load(hdl.isFreed).bool or recoveredCount < minTimers: 340 | newAllTHandles[newIdx] = hdl 341 | inc newIdx 342 | 343 | if atomics.load(hdl.isFreed).bool: 344 | inc recoveredCount 345 | inc freedTimersCount 346 | 347 | allTHandles.delete(allTHandles.low, allTHandles.high) 348 | allTHandles = newAllTHandles 349 | # FIXME: reuse seq 350 | 351 | else: 352 | discard 353 | 354 | else: 355 | if shutdownState == ShutdownState.shutdownRequested: 356 | # probe if all timers are done. if so, enter state doShutdown 357 | # do not consume command queue any more 358 | if runningTimersCount == 0: 359 | shutdownState = ShutdownState.doShutdown 360 | 361 | elif shutdownState == ShutdownState.doShutdown: 362 | for i in allTHandles.low .. allTHandles.high: 363 | let timer = allTHandles[i] 364 | if not timer.isNil: 365 | deinitLock(allTHandles[i].waitLock) 366 | deinitCond(allTHandles[i].waitCond) 367 | 368 | allTHandles.delete(allTHandles.low, allTHandles.high) 369 | signal(sptr.poolShutdownDoneCond) 370 | break # exit worker loop 371 | 372 | # adjust timebase and sleep / msused is in millisecs 373 | # to eliminate jitter 374 | let msused: int = cast[int]((cpuTime() - currTime)*1_000) 375 | if sptr.timebase > msused: 376 | sleep(sptr.timebase - msused) 377 | 378 | 379 | proc createTimerPool(tbase: int): ref TimerPool = 380 | result = new TimerPool 381 | result.timebase = tbase 382 | result.spawningThreadId = getThreadId() 383 | # used by checkIfSpawningThread 384 | initLock(result.poolReqLock) 385 | initCond(result.poolShutdownDoneCond) 386 | withLock(result.poolReqLock): 387 | # lock needed to make compiler happy 388 | result.cmdQueue = deques.initDeque[PMsgPtr](8) 389 | 390 | # public api 391 | type 392 | Tickval* = range[1..int.high] 393 | MinTimerval* = range[1..int.high] 394 | ## integer type used to initialise the timerpool and to set the 395 | ## timeout of the timer 396 | 397 | proc initThreadvar(): void = 398 | threadContext = new PMsg 399 | initCond(threadContext.allocTimerCompleteCond) 400 | initCond(threadContext.poolStatsCompleteCond) 401 | threadContext.cmd = PoolCmd.noOp 402 | 403 | proc deinitThreadvar(): void = 404 | deinitCond(threadContext.allocTimerCompleteCond) 405 | deinitCond(threadContext.poolStatsCompleteCond) 406 | 407 | proc initThreadContext*(tpptr: TimerPoolPtr): void {.raises: [TPError].} = 408 | ## to be called explicit if the pool-accessing thread is not the 409 | ## owner of the timerpool (initialises threadvar globs) 410 | ## 411 | ## raises a TPError if called within the spawning thread 412 | checkIfSpawningThread(tpptr) 413 | initThreadvar() 414 | 415 | proc newTimerPool*(tbase_ms: Tickval = 100, 416 | minFreedTimers: MinTimerval = 5): ref TimerPool = 417 | ## creator proc. 418 | ## The tickval is of milliseconds and 419 | ## the default timebase is 100 milliseconds 420 | ## the default of the mintimers parameter is 5 (shrink_pool leave this 421 | ## minimum amount of freed timers within the pool) 422 | result = createTimerPool(tbase_ms) 423 | initThreadvar() 424 | createThread(result.tickthread, timerPoolWorkLoop, (cast[SomePtr](result), 425 | cast[int](minFreedTimers))) 426 | 427 | proc deinitThreadContext*(tpptr: TimerPoolPtr): void {.raises: [TPError].} = 428 | ## call this proc if the pool-accessing thread should be 429 | ## detached from the timerpool (cleanup threadvar globs) 430 | ## 431 | ## call this proc only if the current thread is not owner of the 432 | ## timerpool. if not a TPError is raised 433 | checkIfSpawningThread(tpptr) 434 | deinitThreadvar() 435 | 436 | proc shutdownTimerPool*(tpref: TimerPoolRef): void = 437 | ## shut down the timerpool (graceful) and frees 438 | ## all resources (timerHandles and the pool itself) 439 | ## 440 | ## this call blocks till all timers are fired 441 | ## also only the spawning/owning thread is allowed to shutdown the pool 442 | ## this is guarded/ensured by the ref-parameter type within the public ctor 443 | threadContext.cmd = PoolCmd.killPool 444 | withLock(tpref.poolReqLock): 445 | tpref.cmdqueue.addLast(cast[PMsgPtr](threadContext)) 446 | wait(tpref.poolShutdownDoneCond, tpref.poolReqLock) 447 | while tpref.cmdqueue.len > 0: 448 | # flush queue and inform possible waiting threads 449 | let pendingcmds = tpref.cmdqueue.popLast() 450 | pendingcmds.reply = PoolReply.abort 451 | 452 | case pendingcmds.cmd 453 | 454 | of requestTimer: 455 | signal(pendingcmds.allocTimerCompleteCond) 456 | of poolStats: 457 | signal(pendingcmds.poolStatsCompleteCond) 458 | else: 459 | discard 460 | 461 | deinitCond(tpref.poolShutdownDoneCond) 462 | deinitLock(tpref.poolReqLock) 463 | deinitThreadvar() 464 | 465 | proc allocTimer*(tpptr: TimerPoolPtr): TimerHandlePtr {.raises: [TPError].} = 466 | ## returns a timerhandle. the timer is always of type:oneshot but could 467 | ## also act as a continous one. in this case the caller needs to reset the 468 | ## alarm to the needed value. This threadsafe call blocks till the request 469 | ## was handled by the pool-tick-thread 470 | ## 471 | ## before calling (if the pool was not spawned by the calling thread) 472 | ## initThreadContext() should be called 473 | ## 474 | ## raises TPError if the pointer parameter is nil and/or the threadContext 475 | ## was not initialised with initThreadContext 476 | checkForNil(tpptr, "allocTimer") 477 | checkForValidThreadContext() 478 | threadContext.cmd = PoolCmd.requestTimer 479 | withLock(tpptr.poolReqLock): 480 | tpptr.cmdqueue.addLast(msgRef2Ptr(threadContext)) 481 | wait(threadContext.allocTimerCompleteCond, tpptr.poolReqLock) 482 | 483 | validatePoolReply(threadContext) 484 | result = threadContext.replyTimerHandlePtr 485 | 486 | proc allocTimer*(tpptr: TimerPoolRef): TimerHandlePtr {.inline, raises: [TPError].} = 487 | return allocTimer(poolRef2Ptr(tpptr)) 488 | 489 | proc deallocTimer*(timerhdl: TimerHandlePtr): void {.raises: [TPError].} = 490 | ## the timer handle is pushed back to the pool. 491 | ## once freed it is not handled by the timerscan any more and its recycled for later use 492 | ## 493 | ## this proc could be called from multiple threads simultaneously. 494 | ## if one ore more threads are waiting on the timers signal all threads 495 | ## gets informed. This call is part of the nonblocking api 496 | ## 497 | ## raises TPError if the pointer parameter is nil 498 | checkForNil(timerhdl, "deallocTimer") 499 | abortWhenTimerFreed(timerhdl, "deallocTimer") 500 | atomics.store[bool](timerhdl.isFreed, true) 501 | 502 | proc setAlarmCounter*(timerhdl: TimerHandlePtr, value: Tickval): void {. 503 | raises: [TPError].} = 504 | ## sets the timers countdown alarm-value to the given one. 505 | ## reset the counter after it´s fired to obtain a continous timer 506 | ## 507 | ## this call is threadsafe and part of the nonblocking-api 508 | ## 509 | ## raises TPError if the pointer parameter is nil or the timer is freed 510 | checkForNil(timerhdl, "setAlarmCounter") 511 | abortWhenTimerFreed(timerhdl, "setAlarmCounter") 512 | atomics.store[int](timerhdl.alarmctr, value) 513 | 514 | proc getAlarmCounter*(timerhdl: TimerHandlePtr): int {.raises: [TPError].} = 515 | ## returns the current value of the alarmcounter 516 | ## could be used for a polling-style-waiting_for_timer_fired 517 | ## 518 | ## this call is threadsafe and part of the nonblocking-api 519 | ## 520 | ## raises TPError if the pointer parameter is nil or the timer already freed 521 | checkForNil(timerhdl, "getAlarmCounter") 522 | abortWhenTimerFreed(timerhdl, "getAlarmCounter") 523 | result = atomics.load[int](timerhdl.alarmctr) 524 | 525 | proc waitForAlarm*(timerhdl: TimerHandlePtr): void {.raises: [TPError].} = 526 | ## blocking wait till the alarmcounter is decremented to 0 527 | ## 528 | ## threadsafe impl and could be called by multiple threads simultaniously 529 | ## 530 | ## raises TPError if the pointer parameter is nil or the timer already freed 531 | checkForNil(timerhdl, "waitForAlarm") 532 | abortWhenTimerFreed(timerhdl, "waitForAlarm") 533 | withLock(timerhdl.waitLock): 534 | waitOnTimerhdl(timerhdl) 535 | 536 | type 537 | PoolStats* = tuple[runningCount: int, 538 | freedCount: int, 539 | inactiveCount: int] 540 | ## container type returned by waitForGetStats. the sum of 541 | ## runningCount,freedCount and inactiveCount is the total amount 542 | ## of timerhandles within the pool 543 | 544 | proc waitForGetStats*(tpptr: TimerPoolPtr): PoolStats {.raises: [TPError].} = 545 | ## fetches some pool statistics for debugging purposes 546 | ## 547 | ## raises TPError if the pointer parameter is nil or the threadContext 548 | ## was not initialized with initThreadContext 549 | checkForNil(tpptr, "waitForGetStats") 550 | checkForValidThreadContext() 551 | threadContext.cmd = PoolCmd.poolStats 552 | withLock(tpptr.poolReqLock): 553 | tpptr.cmdqueue.addLast(msgRef2Ptr(threadContext)) 554 | waitOnStatsComplete(tpptr, threadContext) 555 | 556 | validatePoolReply(threadContext) 557 | result.runningCount = threadContext.statRunningTimers 558 | result.freedCount = threadContext.statFreedTimers 559 | result.inactiveCount = threadContext.statInactiveTimers 560 | 561 | proc shrinkTimerPool*(tpptr: TimerPoolPtr) {.raises: [TPError].} = 562 | ## shrinks the pool of freed Timers. 563 | ## the given minFreedTimers value at pool construction specifies the lower watermark 564 | ## 565 | ## this is a nonblocking call. 566 | ## raises TPError if the pointer parameter is nil and/or the threadContext 567 | ## was not initialised with initThreadContext (only needed if the pool was not 568 | ## spawned by the caller) 569 | checkForNil(tpptr, "shrinkTimerPool") 570 | checkForValidThreadContext() 571 | threadContext.cmd = PoolCmd.shrinkPool 572 | withLock(tpptr.poolReqLock): 573 | tpptr.cmdqueue.addLast(msgRef2Ptr(threadContext)) 574 | -------------------------------------------------------------------------------- /timerpool.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.1.0" 3 | author = "Michael Krauter" 4 | description = "single thread Timerpool implementation in Nim for event purpose" 5 | license = "MIT" 6 | skipDirs = @["tests"] 7 | 8 | # Dependencies 9 | requires "nim >= 0.17.0" 10 | 11 | task test, "running tests": 12 | exec "nim timerpool_tests" --------------------------------------------------------------------------------