Extend OnRemove event #116

Description

@MichaConrad

Status

Implemented events for the following handles

  • System.Runtime.Caching
  • Microsoft.Extensions.Caching.Memory
  • DictionaryCache
  • System.Web cache
  • Redis!

Goals

In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

Implementation concepts

For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

For distributed caches this gets a little bit more tricky.

  • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
    Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
  • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

[Update 2017-03-05]

Implementation details

I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

Redis Specific

For Redis, things get more complicated:

  • There is an explicit configuration option to enable the feature
  • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
  • Per default, this will NOT be enabled.
  • To use the feature, every redis server CacheManager connects to, must be configured properly:
    Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

If not configured in Redis, CacheManager will not receive any events.

I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

Key and Region value limitations

The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

Integration with UpdateMode.UP

If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

Testing

Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
Expire seems to work just great, even in unit tests, see tests

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    Extend OnRemove event #116

    Description

    @MichaConrad

    Status

    Implemented events for the following handles

    • System.Runtime.Caching
    • Microsoft.Extensions.Caching.Memory
    • DictionaryCache
    • System.Web cache
    • Redis!

    Goals

    In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

    But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
    The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

    Implementation concepts

    For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

    For distributed caches this gets a little bit more tricky.

    • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
      Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
    • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

    [Update 2017-03-05]

    Implementation details

    I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

    There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

    For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

    Redis Specific

    For Redis, things get more complicated:

    • There is an explicit configuration option to enable the feature
    • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
    • Per default, this will NOT be enabled.
    • To use the feature, every redis server CacheManager connects to, must be configured properly:
      Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

    If not configured in Redis, CacheManager will not receive any events.

    I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
    If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

    Key and Region value limitations

    The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
    Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
    In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
    Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

    Integration with UpdateMode.UP

    If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

    Testing

    Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
    Expire seems to work just great, even in unit tests, see tests

    Metadata

    Metadata

    Assignees

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      Extend OnRemove event #116

      Description

      @MichaConrad

      Status

      Implemented events for the following handles

      • System.Runtime.Caching
      • Microsoft.Extensions.Caching.Memory
      • DictionaryCache
      • System.Web cache
      • Redis!

      Goals

      In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

      But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
      The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

      Implementation concepts

      For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

      For distributed caches this gets a little bit more tricky.

      • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
        Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
      • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

      [Update 2017-03-05]

      Implementation details

      I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

      There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

      For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

      Redis Specific

      For Redis, things get more complicated:

      • There is an explicit configuration option to enable the feature
      • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
      • Per default, this will NOT be enabled.
      • To use the feature, every redis server CacheManager connects to, must be configured properly:
        Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

      If not configured in Redis, CacheManager will not receive any events.

      I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
      If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

      Key and Region value limitations

      The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
      Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
      In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
      Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

      Integration with UpdateMode.UP

      If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

      Testing

      Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
      Expire seems to work just great, even in unit tests, see tests

      Metadata

      Metadata

      Assignees

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        Extend OnRemove event #116

        Description

        @MichaConrad

        Status

        Implemented events for the following handles

        • System.Runtime.Caching
        • Microsoft.Extensions.Caching.Memory
        • DictionaryCache
        • System.Web cache
        • Redis!

        Goals

        In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

        But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
        The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

        Implementation concepts

        For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

        For distributed caches this gets a little bit more tricky.

        • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
          Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
        • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

        [Update 2017-03-05]

        Implementation details

        I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

        There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

        For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

        Redis Specific

        For Redis, things get more complicated:

        • There is an explicit configuration option to enable the feature
        • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
        • Per default, this will NOT be enabled.
        • To use the feature, every redis server CacheManager connects to, must be configured properly:
          Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

        If not configured in Redis, CacheManager will not receive any events.

        I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
        If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

        Key and Region value limitations

        The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
        Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
        In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
        Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

        Integration with UpdateMode.UP

        If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

        Testing

        Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
        Expire seems to work just great, even in unit tests, see tests

        Metadata

        Metadata

        Assignees

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          Extend OnRemove event #116

          Description

          @MichaConrad

          Status

          Implemented events for the following handles

          • System.Runtime.Caching
          • Microsoft.Extensions.Caching.Memory
          • DictionaryCache
          • System.Web cache
          • Redis!

          Goals

          In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

          But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
          The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

          Implementation concepts

          For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

          For distributed caches this gets a little bit more tricky.

          • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
            Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
          • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

          [Update 2017-03-05]

          Implementation details

          I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

          There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

          For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

          Redis Specific

          For Redis, things get more complicated:

          • There is an explicit configuration option to enable the feature
          • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
          • Per default, this will NOT be enabled.
          • To use the feature, every redis server CacheManager connects to, must be configured properly:
            Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

          If not configured in Redis, CacheManager will not receive any events.

          I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
          If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

          Key and Region value limitations

          The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
          Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
          In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
          Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

          Integration with UpdateMode.UP

          If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

          Testing

          Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
          Expire seems to work just great, even in unit tests, see tests

          Metadata

          Metadata

          Assignees

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            Extend OnRemove event #116

            Description

            @MichaConrad

            Status

            Implemented events for the following handles

            • System.Runtime.Caching
            • Microsoft.Extensions.Caching.Memory
            • DictionaryCache
            • System.Web cache
            • Redis!

            Goals

            In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

            But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
            The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

            Implementation concepts

            For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

            For distributed caches this gets a little bit more tricky.

            • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
              Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
            • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

            [Update 2017-03-05]

            Implementation details

            I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

            There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

            For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

            Redis Specific

            For Redis, things get more complicated:

            • There is an explicit configuration option to enable the feature
            • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
            • Per default, this will NOT be enabled.
            • To use the feature, every redis server CacheManager connects to, must be configured properly:
              Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

            If not configured in Redis, CacheManager will not receive any events.

            I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
            If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

            Key and Region value limitations

            The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
            Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
            In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
            Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

            Integration with UpdateMode.UP

            If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

            Testing

            Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
            Expire seems to work just great, even in unit tests, see tests

            Metadata

            Metadata

            Assignees

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Extend OnRemove event #116

              Description

              @MichaConrad

              Status

              Implemented events for the following handles

              • System.Runtime.Caching
              • Microsoft.Extensions.Caching.Memory
              • DictionaryCache
              • System.Web cache
              • Redis!

              Goals

              In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

              But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
              The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

              Implementation concepts

              For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

              For distributed caches this gets a little bit more tricky.

              • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
                Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
              • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

              [Update 2017-03-05]

              Implementation details

              I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

              There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

              For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

              Redis Specific

              For Redis, things get more complicated:

              • There is an explicit configuration option to enable the feature
              • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
              • Per default, this will NOT be enabled.
              • To use the feature, every redis server CacheManager connects to, must be configured properly:
                Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

              If not configured in Redis, CacheManager will not receive any events.

              I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
              If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

              Key and Region value limitations

              The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
              Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
              In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
              Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

              Integration with UpdateMode.UP

              If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

              Testing

              Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
              Expire seems to work just great, even in unit tests, see tests

              Metadata

              Metadata

              Assignees

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                Extend OnRemove event #116

                Description

                @MichaConrad

                Status

                Implemented events for the following handles

                • System.Runtime.Caching
                • Microsoft.Extensions.Caching.Memory
                • DictionaryCache
                • System.Web cache
                • Redis!

                Goals

                In 0.9.3 and previous versions, OnRemove only triggers if items are removed manually. With version 0.9.2 it also works if any instance of the same cache triggers this event and transports the event via the backplane.

                But one key feature is missing: Trigger OnRemove if a key expires or gets evicted by the underlying cache.
                The in-memory cache handles for System.Runtime.Caching and MS.Extensions.Caching do actually support this, but CacheManager does not make use of it to trigger OnRemove yet.

                Implementation concepts

                For in-memory caches, this is pretty simple because this is usually a build in functionality and already used by CacheManager.

                For distributed caches this gets a little bit more tricky.

                • For Redis, CacheManager has to register and listen to keyspace notifications, a feature which must be enabled explicitly on the redis server.
                  Therefore feature checks might be needed to gracefully disable the event propagation and maybe log warnings on startup (as early as possible)
                • For memcached and couchbase there is actually no way to implement this as memcache doesn't have any feature added since the past x years... meh

                [Update 2017-03-05]

                Implementation details

                I decided to separate the existing OnRemove from the new event as a) the events are triggered differently b) the events are triggered by only one cache handle and not by the manager and c) the passed in arguments are slightly different.

                There will be a new OnRemoveByHandle event which gets triggered only by one cache handle if the underlying cache either detects an expired key or decides to evict a key because of memory pressure or other reasons.

                For the in-memory caches, the implementation was pretty straight forward. Basically, we can listen on callbacks provided by the cache and pass those through.

                Redis Specific

                For Redis, things get more complicated:

                • There is an explicit configuration option to enable the feature
                • Can be configured by code (enableKeyspaceNotifications) or json/xml configuration.
                • Per default, this will NOT be enabled.
                • To use the feature, every redis server CacheManager connects to, must be configured properly:
                  Set notify-keyspace-events to at least Exe. E because we only need keyevents, and x and e for expired and evicted events of keys.

                If not configured in Redis, CacheManager will not receive any events.

                I'm trying to test if the configuration is present, but the run CONFIG commands via the redis client, useAdmin must be set to true! If that's not set, I cannot read the Redis configuration.
                If the check detects missing configuration, warnings will be logged. If the check fails on the client level, a debug message will be logged.

                Key and Region value limitations

                The key stored in Redis holding the CacheItem is build by using <region>:<key> (concatenating region and key separated by :).
                Now, for the event feature to work, I have to retrieve the region and key from the actual key stored in redis (which is just a string).
                In case the key itself contains : and there might not even be a region set, there would be no way to determine the correct key/region. Same goes for the region, if the region contains :.
                Therefore, if the keyevent notification feature is enabled, I will encode the key and region before storing them in Redis (only if the key or region contains :).

                Integration with UpdateMode.UP

                If UpdateMode is set to Up and not None, CacheManager will remove the cache item from cache handles above the one which triggered the event. This is actually pretty cool to automatically sync even two level caches with in-memory in-front of Redis for example.

                Testing

                Testing this is pretty hard, I will probably not be able to 100% validate that all the caches trigger evicts correctly, as that depends on memory pressure.
                Expire seems to work just great, even in unit tests, see tests

                Metadata

                Metadata

                Assignees

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions