git.iko.soy/typeable/thread-local
on master help sign in
Initial. Nothing works yet.
commit41c0442fb049ac930793c793bb87d5446c14a516 authorNick <nick@typeable.io> dateThu 6 Jul 2023, 12:46 (3 years ago)
7 files changed+217
LICENSE+20 example/Main.hs+20 src/Control/Concurrent/ThreadLocal.hs+8 src/Control/Concurrent/ThreadLocal/Internal.hs+58 src/Control/Concurrent/ThreadLocal/Unsafe.hs+9 stack.yaml+67 thread-local.cabal+35
LICENSEnew file+20
@@ -0,0 +1,20 @@
1+Copyright (c) 2023 Nick
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be included
12+in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
example/Main.hsnew file+20
@@ -0,0 +1,20 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+module Main where
3+
4+import Control.Concurrent
5+import Control.Concurrent.ThreadLocal
6+import Control.Monad
7+import System.Random
8+
9+main :: IO ()
10+main = do tls <- newThreadLocal
11+ replicateM_ 1000 (forkIO $ f tls)
12+ threadDelay 1000000
13+
14+f :: ThreadLocal Word -> IO ()
15+f tls = do v :: Word <- randomIO
16+ tid <- myThreadId
17+ insertThreadLocal v tls
18+ v' <- fetchThreadLocal tls
19+ unless (Just v == v') $
20+ fail ("Ooops! " <> show tid <> " expected " <> show v <> " while the fetched value is " <> show v')
src/Control/Concurrent/ThreadLocal.hsnew file+8
@@ -0,0 +1,8 @@
1+module Control.Concurrent.ThreadLocal ( ThreadLocal -- exported opaque
2+ , newThreadLocal
3+ , insertThreadLocal
4+ , fetchThreadLocal
5+ , removeThreadLocal
6+ ) where
7+
8+import Control.Concurrent.ThreadLocal.Internal
src/Control/Concurrent/ThreadLocal/Internal.hsnew file+58
@@ -0,0 +1,58 @@
1+module Control.Concurrent.ThreadLocal.Internal where
2+
3+import Control.Concurrent (ThreadId, myThreadId)
4+import Data.IORef
5+import Data.Map (Map)
6+import qualified Data.Map.Strict as Map
7+import System.Mem.StableName
8+import System.Mem.Weak
9+
10+type ThreadName = StableName ThreadId
11+
12+data LocalVal a = LocalVal !(Weak ThreadId) !a
13+
14+isSameThread :: ThreadId -> Weak ThreadId -> IO Bool
15+isSameThread tid wid = do mtid' <- deRefWeak wid
16+ return $ case mtid' of
17+ Nothing -> False
18+ Just tid' -> tid == tid'
19+
20+newtype ThreadLocal a = ThreadLocal { values :: IORef (Map ThreadName (LocalVal a))
21+ }
22+
23+-- | Create a new thread local storage of type 'a'
24+newThreadLocal :: IO (ThreadLocal a)
25+newThreadLocal = do ref <- newIORef Map.empty
26+ return (ThreadLocal ref)
27+
28+-- | Insert a value for the current thread
29+insertThreadLocal :: a -> ThreadLocal a -> IO ()
30+insertThreadLocal v (ThreadLocal ref) = do tid <- myThreadId
31+ name <- makeStableName tid
32+ wid <- mkWeakPtr tid Nothing
33+ atomicModifyIORef' ref $ \m ->
34+ let m' = Map.insert name (LocalVal wid v) m
35+ in (m',())
36+
37+-- | Fetch a value for the current thread. If none was inserted previously, return Nothing.
38+fetchThreadLocal :: ThreadLocal a -> IO (Maybe a)
39+fetchThreadLocal (ThreadLocal ref) = do tid <- myThreadId
40+ name <- makeStableName tid
41+ m <- readIORef ref
42+ case Map.lookup name m of
43+ Just (LocalVal wid v) ->
44+ do st <- isSameThread tid wid
45+ if st
46+ then return (Just v)
47+ -- Account for the fact that a string representation of
48+ -- a ThreadId can result in a collision.
49+ -- If one occurs, remove the old data.
50+ else Nothing <$ removeThreadLocal (ThreadLocal ref)
51+ Nothing -> return Nothing
52+
53+-- | Remove the currently held value for the current thread.
54+removeThreadLocal :: ThreadLocal a -> IO ()
55+removeThreadLocal (ThreadLocal ref) = do tid <- myThreadId
56+ name <- makeStableName tid
57+ atomicModifyIORef' ref $ \m ->
58+ (Map.delete name m, ())
src/Control/Concurrent/ThreadLocal/Unsafe.hsnew file+9
@@ -0,0 +1,9 @@
1+module Control.Concurrent.ThreadLocal.Unsafe where
2+
3+--import Control.Concurrent.ThreadLocal.Internal
4+--import Data.Map (Map)
5+--import qualified Data.Map (Map)
6+
7+
8+--dumpThreadLocal :: ThreadLocal a -> IO (Map ThreadId a)
9+--dumpThreadLocal (ThreadLocal ref) = do m <-
stack.yamlnew file+67
@@ -0,0 +1,67 @@
1+# This file was automatically generated by 'stack init'
2+#
3+# Some commonly used options have been documented as comments in this file.
4+# For advanced use and comprehensive documentation of the format, please see:
5+# https://docs.haskellstack.org/en/stable/yaml_configuration/
6+
7+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
8+# A snapshot resolver dictates the compiler version and the set of packages
9+# to be used for project dependencies. For example:
10+#
11+# resolver: lts-3.5
12+# resolver: nightly-2015-09-21
13+# resolver: ghc-7.10.2
14+#
15+# The location of a snapshot can be provided as a file or url. Stack assumes
16+# a snapshot provided as a file might change, whereas a url resource does not.
17+#
18+# resolver: ./custom-snapshot.yaml
19+# resolver: https://example.com/snapshots/2018-01-01.yaml
20+resolver:
21+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/1.yaml
22+
23+# User packages to be built.
24+# Various formats can be used as shown in the example below.
25+#
26+# packages:
27+# - some-directory
28+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
29+# subdirs:
30+# - auto-update
31+# - wai
32+packages:
33+- .
34+# Dependency packages to be pulled from upstream that are not in the resolver.
35+# These entries can reference officially published versions as well as
36+# forks / in-progress versions pinned to a git hash. For example:
37+#
38+# extra-deps:
39+# - acme-missiles-0.3
40+# - git: https://github.com/commercialhaskell/stack.git
41+# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
42+#
43+# extra-deps: []
44+
45+# Override default flag values for local packages and extra-deps
46+# flags: {}
47+
48+# Extra package databases containing global packages
49+# extra-package-dbs: []
50+
51+# Control whether we use the GHC we find on the path
52+# system-ghc: true
53+#
54+# Require a specific version of stack, using version ranges
55+# require-stack-version: -any # Default
56+# require-stack-version: ">=2.9"
57+#
58+# Override the architecture used by stack, especially useful on Windows
59+# arch: i386
60+# arch: x86_64
61+#
62+# Extra directories used by stack for building
63+# extra-include-dirs: [/path/to/dir]
64+# extra-lib-dirs: [/path/to/dir]
65+#
66+# Allow a newer minor version of GHC than the snapshot specifies
67+# compiler-check: newer-minor
thread-local.cabalnew file+35
@@ -0,0 +1,35 @@
1+cabal-version: 3.0
2+name: thread-local
3+version: 0.1
4+synopsis: Thread local storage
5+-- description:
6+license: MIT
7+license-file: LICENSE
8+author: Nick
9+maintainer: nick@typeable.io
10+-- copyright:
11+category: Control
12+build-type: Simple
13+-- extra-source-files:
14+
15+common warnings
16+ ghc-options: -Wall
17+
18+library
19+ import: warnings
20+ exposed-modules: Control.Concurrent.ThreadLocal
21+ Control.Concurrent.ThreadLocal.Unsafe
22+ other-modules: Control.Concurrent.ThreadLocal.Internal
23+ build-depends: base >=4.16 && < 5,
24+ containers >= 0.6
25+ hs-source-dirs: src
26+ default-language: Haskell2010
27+
28+executable example
29+ import: warnings
30+ Main-Is: Main.hs
31+ build-depends: base >= 4.16 && < 5,
32+ thread-local,
33+ random
34+ hs-source-dirs: example
35+ default-language: Haskell2010