For bound keys with 'Key released' functionality, I used two dictionaries. The dictionaries stored which key was down each tick by recording its keycode (you can also store the player ID in the value to simplify other code I would have thought). At the end of each tick the game loop would evaluate which dictionary keys were present this tick, and compare it to the keys present in the previous tick.
If the key is now present, but was previously absent, call a function that corresponds to 'Key pressed'
If the key is present in both, call a function that corresponds to 'Key down'
If the key was present in the previous tick, and absent in the current tick, call ... 'Key released'!