├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── ServiceFabricDistributedCache.sln
├── assets
├── icon-128x128.png
├── icon-64x64.png
└── microsoft-service-fabric-distributed-cache.png
├── azure-pipelines.yml
├── examples
├── DistributedCache
│ ├── ApplicationPackageRoot
│ │ └── ApplicationManifest.xml
│ ├── ApplicationParameters
│ │ ├── Cloud.xml
│ │ ├── Local.1Node.xml
│ │ └── Local.5Node.xml
│ ├── DistributedCache.sfproj
│ ├── PublishProfiles
│ │ ├── Cloud.xml
│ │ ├── Local.1Node.xml
│ │ └── Local.5Node.xml
│ ├── Scripts
│ │ └── Deploy-FabricApplication.ps1
│ └── packages.config
├── LoadTestApp
│ ├── LoadTestApp.csproj
│ ├── Program.cs
│ └── Worker.cs
└── Services
│ ├── ClientApp
│ ├── ClientApp.cs
│ ├── ClientApp.csproj
│ ├── Controllers
│ │ └── CacheDemoController.cs
│ ├── PackageRoot
│ │ ├── Config
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Program.cs
│ ├── ServiceEventSource.cs
│ ├── Startup.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
│ └── DistributedCacheStore
│ ├── DistributedCacheStore.cs
│ ├── DistributedCacheStore.csproj
│ ├── PackageRoot
│ ├── Config
│ │ └── Settings.xml
│ └── ServiceManifest.xml
│ ├── Program.cs
│ └── ServiceEventSource.cs
├── src
└── SoCreate.Extensions.Caching.ServiceFabric
│ ├── CacheStoreException.cs
│ ├── CacheStoreMetadata.cs
│ ├── CacheStoreNotFoundException.cs
│ ├── CachedItem.cs
│ ├── DistributedCacheStoreLocator.cs
│ ├── DistributedCacheStoreService.cs
│ ├── IDistributedCacheStoreLocator.cs
│ ├── IServiceFabricCacheStoreService.cs
│ ├── LinkedDictionaryHelper.cs
│ ├── RetryHelper.cs
│ ├── ServiceFabricCacheOptions.cs
│ ├── ServiceFabricCachingServicesExtensions.cs
│ ├── ServiceFabricDistributedCache.cs
│ └── SoCreate.Extensions.Caching.ServiceFabric.csproj
└── tests
└── SoCreate.Extensions.Caching.Tests
├── AutoMoqDataAttribute.cs
├── DistributedCacheStoreServiceTest.cs
├── LinkedDictionaryHelperTest.cs
└── SoCreate.Extensions.Caching.Tests.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.0 (2019-12-16)
2 |
3 |
4 |
5 | ### Upgrade
6 | * **Service Fabric** Upgrade Service Fabric to SDK v4.
7 | * **Example** Upgrade example project to .NET Core 3.1.
8 |
9 | # 1.0.7 (2019-03-11)
10 |
11 |
12 |
13 | ### Bug Fixes
14 | * **caching:** Fix issue with getting a cached item not working consistently in the senario where your cache store has multiple partitions
15 | * **caching:** Add better error message when client can't find the cache store in the Service Fabric cluster
16 |
17 | # 1.0.6 (2019-03-06)
18 |
19 |
20 |
21 | ### Bug Fixes
22 | * **caching:** Fix issue with non-expired cached items getting removed from cache if cache is full and cached item was not recently retrieved
23 | * **caching:** Fix issue with cache being cleared when cache store process was restarted
24 |
25 | # 1.0.5 (2019-03-01)
26 |
27 |
28 |
29 | ### Bug Fixes
30 | * **package information:** Update Nuget package information
31 |
32 | # 1.0.4 (2019-02-28)
33 |
34 |
35 |
36 | ### Bug Fixes
37 | * **caching:** Fix issue with Set not keeping cached items expiration settings
38 | * **caching:** Fix issue with cache items set to absolute expiration not being updated as recently retrieved
39 |
40 | # 1.0.1 (2019-02-26)
41 |
42 | ### Bug Fixes
43 | * **package dependencies:** Fix dependencies for Nuget package
44 |
45 | # 1.0.0 (2019-02-26)
46 |
47 |
48 |
49 | ### Initial Release
50 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Service Fabric Distributed Cache
2 |
3 | - [Issues and Bugs](#issue)
4 | - [Request/Add a Feature](#feature)
5 |
6 | ## Found an Issue?
7 | If you find a bug in the source code or a mistake in the documentation, you can help us by
8 | [submitting an issue][submitissue] to our [GitHub Repository][github]. Even better,
9 | you can [submit a Pull Request](#submit-pr) with a fix.
10 |
11 | ## Want a Feature?
12 | You can *request* a new feature by [submitting an issue][submitissue] to our [GitHub
13 | Repository][github]. If you would like to *implement* a new feature, please submit an issue with
14 | a proposal for your work first, to be sure that we can use it.
15 | Please consider what kind of change it is:
16 |
17 | * For a **Major Feature**, first open an issue and outline your proposal so that it can be
18 | discussed. This will also allow us to better coordinate our efforts, prevent duplication of work,
19 | and help you to craft the change so that it is successfully accepted into the project.
20 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
21 |
22 | ### Submitting a Pull Request (PR)
23 | Before you submit your Pull Request (PR) consider the following guidelines:
24 |
25 | * Search [GitHub](https://github.com/socreate/service-fabric-distributed-cache/pulls) for an open or closed PR
26 | that relates to your submission. You don't want to duplicate effort.
27 | * Make your changes in a new git branch:
28 |
29 | ```shell
30 | git checkout -b my-fix-branch master
31 | ```
32 |
33 | * Create your patch.
34 | * Commit your changes using a descriptive commit message.
35 | * Push your branch to GitHub:
36 |
37 | ```shell
38 | git push origin my-fix-branch
39 | ```
40 |
41 | * In GitHub, send a pull request to `service-fabric-distributed-cache:master`.
42 | * If we suggest changes then:
43 | * Make the required updates.
44 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
45 |
46 | ```shell
47 | git rebase master -i
48 | git push -f
49 | ```
50 |
51 | That's it! Thank you for your contribution!
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 SoCreate
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 | # [](./assets/microsoft-service-fabric-distributed-cache.png)
2 |
3 | # Service Fabric Distributed Cache
4 | An implementation of the IDistributedCache that uses a Stateful Reliable Service Fabric service to act as the cache store. You can use this library to setup a distributed cache and use Service Fabric instead of Redis or SQL Server.
5 |
6 | [](https://dev.azure.com/SoCreate/Open%20Source%20Projects/_build/latest?definitionId=17&branchName=master)
7 | [](https://www.nuget.org/packages/SoCreate.Extensions.Caching.ServiceFabric/)
8 |
9 | ## Documentation ()
10 |
11 | ## Contributing
12 |
13 | Help Service Fabric Distributed Cache by contributing!
14 |
15 | ### [Contributing Guide](./CONTRIBUTING.md)
16 |
17 | Please read our [contributing guide](./CONTRIBUTING.md) to learn about filing issues, submitting PRs, and building
18 | Service Fabric Distributed Cache.
19 |
20 | ### License
21 |
22 | Service Fabric Distributed Cache is [MIT licensed](./LICENSE).
23 |
24 | ## Latest Changes
25 |
26 | [Service Fabric Distributed Cache Changelog](./CHANGELOG.md)
27 |
--------------------------------------------------------------------------------
/ServiceFabricDistributedCache.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29519.181
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "DistributedCache", "examples\DistributedCache\DistributedCache.sfproj", "{083FA0A4-F720-4F9E-97D1-77EB86A51F79}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientApp", "examples\Services\ClientApp\ClientApp.csproj", "{0B98E889-AC0D-4772-A7E5-BDCC204F1A17}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistributedCacheStore", "examples\Services\DistributedCacheStore\DistributedCacheStore.csproj", "{78084B43-5F67-4F8E-BDE2-4D788A9C9B09}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SoCreate.Extensions.Caching.ServiceFabric", "src\SoCreate.Extensions.Caching.ServiceFabric\SoCreate.Extensions.Caching.ServiceFabric.csproj", "{2CD13B96-A04B-4886-A32F-646DF237820E}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SoCreate.Extensions.Caching.Tests", "tests\SoCreate.Extensions.Caching.Tests\SoCreate.Extensions.Caching.Tests.csproj", "{E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{7F2A2566-D52A-49FD-AB75-C1145259F5F1}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoadTestApp", "examples\LoadTestApp\LoadTestApp.csproj", "{4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{03079911-D689-4300-80F5-84040518D04C}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BB3EDFF6-4B21-4562-8B9B-3C36A8089EF3}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Debug|x64 = Debug|x64
28 | Release|Any CPU = Release|Any CPU
29 | Release|x64 = Release|x64
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|Any CPU.ActiveCfg = Debug|x64
33 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|Any CPU.Build.0 = Debug|x64
34 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|Any CPU.Deploy.0 = Debug|x64
35 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|x64.ActiveCfg = Debug|x64
36 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|x64.Build.0 = Debug|x64
37 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Debug|x64.Deploy.0 = Debug|x64
38 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Release|Any CPU.ActiveCfg = Release|x64
39 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Release|x64.ActiveCfg = Release|x64
40 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Release|x64.Build.0 = Release|x64
41 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79}.Release|x64.Deploy.0 = Release|x64
42 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Debug|x64.ActiveCfg = Debug|Any CPU
45 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Debug|x64.Build.0 = Debug|Any CPU
46 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Release|x64.ActiveCfg = Release|Any CPU
49 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17}.Release|x64.Build.0 = Release|Any CPU
50 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Debug|x64.ActiveCfg = Debug|Any CPU
53 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Debug|x64.Build.0 = Debug|Any CPU
54 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Release|x64.ActiveCfg = Release|Any CPU
57 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09}.Release|x64.Build.0 = Release|Any CPU
58 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Debug|x64.ActiveCfg = Debug|Any CPU
61 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Debug|x64.Build.0 = Debug|Any CPU
62 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Release|x64.ActiveCfg = Release|Any CPU
65 | {2CD13B96-A04B-4886-A32F-646DF237820E}.Release|x64.Build.0 = Release|Any CPU
66 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Debug|x64.ActiveCfg = Debug|Any CPU
69 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Debug|x64.Build.0 = Debug|Any CPU
70 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Release|x64.ActiveCfg = Release|Any CPU
73 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E}.Release|x64.Build.0 = Release|Any CPU
74 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
75 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
76 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Debug|x64.ActiveCfg = Debug|Any CPU
77 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Debug|x64.Build.0 = Debug|Any CPU
78 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
79 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Release|Any CPU.Build.0 = Release|Any CPU
80 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Release|x64.ActiveCfg = Release|Any CPU
81 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3}.Release|x64.Build.0 = Release|Any CPU
82 | EndGlobalSection
83 | GlobalSection(SolutionProperties) = preSolution
84 | HideSolutionNode = FALSE
85 | EndGlobalSection
86 | GlobalSection(NestedProjects) = preSolution
87 | {083FA0A4-F720-4F9E-97D1-77EB86A51F79} = {7F2A2566-D52A-49FD-AB75-C1145259F5F1}
88 | {0B98E889-AC0D-4772-A7E5-BDCC204F1A17} = {7F2A2566-D52A-49FD-AB75-C1145259F5F1}
89 | {78084B43-5F67-4F8E-BDE2-4D788A9C9B09} = {7F2A2566-D52A-49FD-AB75-C1145259F5F1}
90 | {2CD13B96-A04B-4886-A32F-646DF237820E} = {03079911-D689-4300-80F5-84040518D04C}
91 | {E5B6BE91-0114-4C98-8AEA-96C5A7E5026E} = {BB3EDFF6-4B21-4562-8B9B-3C36A8089EF3}
92 | {4FDBA5FD-14B5-4B03-85C6-3685CBE610D3} = {7F2A2566-D52A-49FD-AB75-C1145259F5F1}
93 | EndGlobalSection
94 | GlobalSection(ExtensibilityGlobals) = postSolution
95 | SolutionGuid = {CC598FD8-CADC-41C7-B6C1-B9BAB7A6496F}
96 | EndGlobalSection
97 | EndGlobal
98 |
--------------------------------------------------------------------------------
/assets/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoCreate/service-fabric-distributed-cache/3d6a925ee0fa4f10105cf43b0d05caab88614b48/assets/icon-128x128.png
--------------------------------------------------------------------------------
/assets/icon-64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoCreate/service-fabric-distributed-cache/3d6a925ee0fa4f10105cf43b0d05caab88614b48/assets/icon-64x64.png
--------------------------------------------------------------------------------
/assets/microsoft-service-fabric-distributed-cache.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoCreate/service-fabric-distributed-cache/3d6a925ee0fa4f10105cf43b0d05caab88614b48/assets/microsoft-service-fabric-distributed-cache.png
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | pr: none
4 | pool:
5 | vmImage: ubuntu-latest
6 | variables:
7 | buildConfiguration: Release
8 | projectPaths: src/**/*.csproj
9 | testProjectPaths: 'tests/**/*[Tt]ests/*.csproj'
10 | steps:
11 | - task: UseDotNet@2
12 | displayName: 'Install .NET 6'
13 | inputs:
14 | version: 6.0.x
15 | - task: DotNetCoreCLI@2
16 | displayName: Test
17 | inputs:
18 | command: test
19 | projects: $(testProjectPaths)
20 | arguments: '--configuration $(BuildConfiguration)'
21 | - task: DotNetCoreCLI@2
22 | displayName: Restore
23 | inputs:
24 | command: restore
25 | projects: $(projectPaths)
26 | - task: DotNetCoreCLI@2
27 | displayName: Build
28 | inputs:
29 | projects: $(projectPaths)
30 | arguments: '--configuration $(buildConfiguration)'
31 | - task: DotNetCoreCLI@2
32 | displayName: Pack
33 | inputs:
34 | command: pack
35 | packagesToPack: $(projectPaths)
36 | nobuild: true
37 | - task: NuGetToolInstaller@0
38 | displayName: Use NuGet 4.9.3
39 | inputs:
40 | versionSpec: 4.9.3
41 | - task: NuGetCommand@2
42 | displayName: NuGet push
43 | inputs:
44 | command: push
45 | nuGetFeedType: external
46 | publishFeedCredentials: Public Nuget
47 | - task: PublishBuildArtifacts@1
48 | displayName: Publish Artifact
49 | inputs:
50 | PathtoPublish: $(build.artifactstagingdirectory)
51 |
--------------------------------------------------------------------------------
/examples/DistributedCache/ApplicationPackageRoot/ApplicationManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/examples/DistributedCache/ApplicationParameters/Cloud.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/DistributedCache/ApplicationParameters/Local.1Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/DistributedCache/ApplicationParameters/Local.5Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/DistributedCache/DistributedCache.sfproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 083fa0a4-f720-4f9e-97d1-77eb86a51f79
6 | 2.4
7 | 1.5
8 | 1.7.1
9 | v4.7.2
10 |
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Service Fabric Tools\Microsoft.VisualStudio.Azure.Fabric.ApplicationProject.targets
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/examples/DistributedCache/PublishProfiles/Cloud.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/DistributedCache/PublishProfiles/Local.1Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/DistributedCache/PublishProfiles/Local.5Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/DistributedCache/Scripts/Deploy-FabricApplication.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Deploys a Service Fabric application type to a cluster.
4 |
5 | .DESCRIPTION
6 | This script deploys a Service Fabric application type to a cluster. It is invoked by Visual Studio when deploying a Service Fabric Application project.
7 |
8 | .NOTES
9 | WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary.
10 |
11 | .PARAMETER PublishProfileFile
12 | Path to the file containing the publish profile.
13 |
14 | .PARAMETER ApplicationPackagePath
15 | Path to the folder of the packaged Service Fabric application.
16 |
17 | .PARAMETER DeployOnly
18 | Indicates that the Service Fabric application should not be created or upgraded after registering the application type.
19 |
20 | .PARAMETER ApplicationParameter
21 | Hashtable of the Service Fabric application parameters to be used for the application.
22 |
23 | .PARAMETER UnregisterUnusedApplicationVersionsAfterUpgrade
24 | Indicates whether to unregister any unused application versions that exist after an upgrade is finished.
25 |
26 | .PARAMETER OverrideUpgradeBehavior
27 | Indicates the behavior used to override the upgrade settings specified by the publish profile.
28 | 'None' indicates that the upgrade settings will not be overridden.
29 | 'ForceUpgrade' indicates that an upgrade will occur with default settings, regardless of what is specified in the publish profile.
30 | 'VetoUpgrade' indicates that an upgrade will not occur, regardless of what is specified in the publish profile.
31 |
32 | .PARAMETER UseExistingClusterConnection
33 | Indicates that the script should make use of an existing cluster connection that has already been established in the PowerShell session. The cluster connection parameters configured in the publish profile are ignored.
34 |
35 | .PARAMETER OverwriteBehavior
36 | Overwrite Behavior if an application exists in the cluster with the same name. Available Options are Never, Always, SameAppTypeAndVersion. This setting is not applicable when upgrading an application.
37 | 'Never' will not remove the existing application. This is the default behavior.
38 | 'Always' will remove the existing application even if its Application type and Version is different from the application being created.
39 | 'SameAppTypeAndVersion' will remove the existing application only if its Application type and Version is same as the application being created.
40 |
41 | .PARAMETER SkipPackageValidation
42 | Switch signaling whether the package should be validated or not before deployment.
43 |
44 | .PARAMETER SecurityToken
45 | A security token for authentication to cluster management endpoints. Used for silent authentication to clusters that are protected by Azure Active Directory.
46 |
47 | .PARAMETER CopyPackageTimeoutSec
48 | Timeout in seconds for copying application package to image store.
49 |
50 | .EXAMPLE
51 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug'
52 |
53 | Deploy the application using the default package location for a Debug build.
54 |
55 | .EXAMPLE
56 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -DoNotCreateApplication
57 |
58 | Deploy the application but do not create the application instance.
59 |
60 | .EXAMPLE
61 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'}
62 |
63 | Deploy the application by providing values for parameters that are defined in the application manifest.
64 | #>
65 |
66 | Param
67 | (
68 | [String]
69 | $PublishProfileFile,
70 |
71 | [String]
72 | $ApplicationPackagePath,
73 |
74 | [Switch]
75 | $DeployOnly,
76 |
77 | [Hashtable]
78 | $ApplicationParameter,
79 |
80 | [Boolean]
81 | $UnregisterUnusedApplicationVersionsAfterUpgrade,
82 |
83 | [String]
84 | [ValidateSet('None', 'ForceUpgrade', 'VetoUpgrade')]
85 | $OverrideUpgradeBehavior = 'None',
86 |
87 | [Switch]
88 | $UseExistingClusterConnection,
89 |
90 | [String]
91 | [ValidateSet('Never','Always','SameAppTypeAndVersion')]
92 | $OverwriteBehavior = 'Never',
93 |
94 | [Switch]
95 | $SkipPackageValidation,
96 |
97 | [String]
98 | $SecurityToken,
99 |
100 | [int]
101 | $CopyPackageTimeoutSec,
102 |
103 | [int]
104 | $RegisterApplicationTypeTimeoutSec
105 | )
106 |
107 | function Read-XmlElementAsHashtable
108 | {
109 | Param (
110 | [System.Xml.XmlElement]
111 | $Element
112 | )
113 |
114 | $hashtable = @{}
115 | if ($Element.Attributes)
116 | {
117 | $Element.Attributes |
118 | ForEach-Object {
119 | $boolVal = $null
120 | if ([bool]::TryParse($_.Value, [ref]$boolVal)) {
121 | $hashtable[$_.Name] = $boolVal
122 | }
123 | else {
124 | $hashtable[$_.Name] = $_.Value
125 | }
126 | }
127 | }
128 |
129 | return $hashtable
130 | }
131 |
132 | function Read-PublishProfile
133 | {
134 | Param (
135 | [ValidateScript({Test-Path $_ -PathType Leaf})]
136 | [String]
137 | $PublishProfileFile
138 | )
139 |
140 | $publishProfileXml = [Xml] (Get-Content $PublishProfileFile)
141 | $publishProfile = @{}
142 |
143 | $publishProfile.ClusterConnectionParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("ClusterConnectionParameters")
144 | $publishProfile.UpgradeDeployment = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment")
145 | $publishProfile.CopyPackageParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("CopyPackageParameters")
146 | $publishProfile.RegisterApplicationParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("RegisterApplicationParameters")
147 |
148 | if ($publishProfileXml.PublishProfile.Item("UpgradeDeployment"))
149 | {
150 | $publishProfile.UpgradeDeployment.Parameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment").Item("Parameters")
151 | if ($publishProfile.UpgradeDeployment["Mode"])
152 | {
153 | $publishProfile.UpgradeDeployment.Parameters[$publishProfile.UpgradeDeployment["Mode"]] = $true
154 | }
155 | }
156 |
157 | $publishProfileFolder = (Split-Path $PublishProfileFile)
158 | $publishProfile.ApplicationParameterFile = [System.IO.Path]::Combine($PublishProfileFolder, $publishProfileXml.PublishProfile.ApplicationParameterFile.Path)
159 |
160 | return $publishProfile
161 | }
162 |
163 | $LocalFolder = (Split-Path $MyInvocation.MyCommand.Path)
164 |
165 | if (!$PublishProfileFile)
166 | {
167 | $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml"
168 | }
169 |
170 | if (!$ApplicationPackagePath)
171 | {
172 | $ApplicationPackagePath = "$LocalFolder\..\pkg\Release"
173 | }
174 |
175 | $ApplicationPackagePath = Resolve-Path $ApplicationPackagePath
176 |
177 | $publishProfile = Read-PublishProfile $PublishProfileFile
178 |
179 | if (-not $UseExistingClusterConnection)
180 | {
181 | $ClusterConnectionParameters = $publishProfile.ClusterConnectionParameters
182 | if ($SecurityToken)
183 | {
184 | $ClusterConnectionParameters["SecurityToken"] = $SecurityToken
185 | }
186 |
187 | try
188 | {
189 | [void](Connect-ServiceFabricCluster @ClusterConnectionParameters)
190 | }
191 | catch [System.Fabric.FabricObjectClosedException]
192 | {
193 | Write-Warning "Service Fabric cluster may not be connected."
194 | throw
195 | }
196 | }
197 |
198 | $RegKey = "HKLM:\SOFTWARE\Microsoft\Service Fabric SDK"
199 | $ModuleFolderPath = (Get-ItemProperty -Path $RegKey -Name FabricSDKPSModulePath).FabricSDKPSModulePath
200 | Import-Module "$ModuleFolderPath\ServiceFabricSDK.psm1"
201 |
202 | $IsUpgrade = ($publishProfile.UpgradeDeployment -and $publishProfile.UpgradeDeployment.Enabled -and $OverrideUpgradeBehavior -ne 'VetoUpgrade') -or $OverrideUpgradeBehavior -eq 'ForceUpgrade'
203 |
204 | $PublishParameters = @{
205 | 'ApplicationPackagePath' = $ApplicationPackagePath
206 | 'ApplicationParameterFilePath' = $publishProfile.ApplicationParameterFile
207 | 'ApplicationParameter' = $ApplicationParameter
208 | 'ErrorAction' = 'Stop'
209 | }
210 |
211 | if ($publishProfile.CopyPackageParameters.CopyPackageTimeoutSec)
212 | {
213 | $PublishParameters['CopyPackageTimeoutSec'] = $publishProfile.CopyPackageParameters.CopyPackageTimeoutSec
214 | }
215 |
216 | if ($publishProfile.CopyPackageParameters.CompressPackage)
217 | {
218 | $PublishParameters['CompressPackage'] = $publishProfile.CopyPackageParameters.CompressPackage
219 | }
220 |
221 | if ($publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec)
222 | {
223 | $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec
224 | }
225 |
226 | # CopyPackageTimeoutSec parameter overrides the value from the publish profile
227 | if ($CopyPackageTimeoutSec)
228 | {
229 | $PublishParameters['CopyPackageTimeoutSec'] = $CopyPackageTimeoutSec
230 | }
231 |
232 | # RegisterApplicationTypeTimeoutSec parameter overrides the value from the publish profile
233 | if ($RegisterApplicationTypeTimeoutSec)
234 | {
235 | $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $RegisterApplicationTypeTimeoutSec
236 | }
237 |
238 | if ($IsUpgrade)
239 | {
240 | $Action = "RegisterAndUpgrade"
241 | if ($DeployOnly)
242 | {
243 | $Action = "Register"
244 | }
245 |
246 | $UpgradeParameters = $publishProfile.UpgradeDeployment.Parameters
247 |
248 | if ($OverrideUpgradeBehavior -eq 'ForceUpgrade')
249 | {
250 | # Warning: Do not alter these upgrade parameters. It will create an inconsistency with Visual Studio's behavior.
251 | $UpgradeParameters = @{ UnmonitoredAuto = $true; Force = $true }
252 | }
253 |
254 | $PublishParameters['Action'] = $Action
255 | $PublishParameters['UpgradeParameters'] = $UpgradeParameters
256 | $PublishParameters['UnregisterUnusedVersions'] = $UnregisterUnusedApplicationVersionsAfterUpgrade
257 |
258 | Publish-UpgradedServiceFabricApplication @PublishParameters
259 | }
260 | else
261 | {
262 | $Action = "RegisterAndCreate"
263 | if ($DeployOnly)
264 | {
265 | $Action = "Register"
266 | }
267 |
268 | $PublishParameters['Action'] = $Action
269 | $PublishParameters['OverwriteBehavior'] = $OverwriteBehavior
270 | $PublishParameters['SkipPackageValidation'] = $SkipPackageValidation
271 |
272 | Publish-NewServiceFabricApplication @PublishParameters
273 | }
--------------------------------------------------------------------------------
/examples/DistributedCache/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/LoadTestApp/LoadTestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/LoadTestApp/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.Hosting;
3 | using System;
4 |
5 | namespace LoadTestApp
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | CreateHostBuilder(args).Build().Run();
12 | }
13 |
14 | public static IHostBuilder CreateHostBuilder(string[] args) =>
15 | Host.CreateDefaultBuilder(args)
16 | .ConfigureServices((hostContext, services) =>
17 | {
18 | services.AddDistributedServiceFabricCache(options => options.RetryTimeout = TimeSpan.FromSeconds(5));
19 | services.AddHostedService();
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/LoadTestApp/Worker.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Caching.Distributed;
2 | using Microsoft.Extensions.Hosting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace LoadTestApp
11 | {
12 | class Worker : IHostedService
13 | {
14 | private readonly IDistributedCache _distributedCache;
15 | private Stopwatch _stopWatch = new Stopwatch();
16 | private List _timeMeasures = new List();
17 |
18 | public Worker(IDistributedCache distributedCache)
19 | {
20 | _distributedCache = distributedCache;
21 | }
22 | public async Task StartAsync(CancellationToken cancellationToken)
23 | {
24 | byte[] bytes = Encoding.ASCII.GetBytes("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum");
25 | var options = new DistributedCacheEntryOptions();
26 | options.SlidingExpiration = TimeSpan.FromSeconds(20);
27 |
28 | var tasks = new List>();
29 |
30 | for (var i = 0; i < 5000; i++)
31 | {
32 | cancellationToken.ThrowIfCancellationRequested();
33 | _timeMeasures.Add(MeasureTime(() => {
34 | _distributedCache.Set($"SetKey2-{i}", bytes, options);
35 | }, "SetKey"));
36 | }
37 |
38 | for (var i = 0; i < 5000; i++)
39 | {
40 | cancellationToken.ThrowIfCancellationRequested();
41 | _timeMeasures.Add(await MeasureTimeAsync(async () => {
42 | await _distributedCache.SetAsync($"SetAsyncAwaitedKey2-{i}", bytes, options, cancellationToken);
43 | }, "SetAsyncAwaitedKey"));
44 | }
45 |
46 | for (var i = 0; i < 5000; i++)
47 | {
48 | cancellationToken.ThrowIfCancellationRequested();
49 | tasks.Add(MeasureTimeAsync(async () => {
50 | await _distributedCache.SetAsync($"SetAsyncAwaitedWhenAllKey2-{i}", bytes, options, cancellationToken);
51 | }, "SetAsyncAwaitedWhenAllKey"));
52 | }
53 | _timeMeasures.AddRange(await Task.WhenAll(tasks));
54 |
55 | foreach (var timeMeasure in _timeMeasures)
56 | {
57 | cancellationToken.ThrowIfCancellationRequested();
58 | Console.WriteLine(timeMeasure);
59 | }
60 |
61 | Console.WriteLine("Done!");
62 | Console.ReadKey();
63 | }
64 |
65 | public Task StopAsync(CancellationToken cancellationToken)
66 | {
67 | return Task.CompletedTask;
68 | }
69 |
70 | private async Task MeasureTimeAsync(Func action, string label)
71 | {
72 | var stopWatch = new Stopwatch();
73 | stopWatch.Start();
74 | await action();
75 | _stopWatch.Stop();
76 | return $"{label} {stopWatch.ElapsedMilliseconds}";
77 | }
78 |
79 | private string MeasureTime(Action action, string label)
80 | {
81 | var stopWatch = new Stopwatch();
82 | stopWatch.Start();
83 | action();
84 | _stopWatch.Stop();
85 | return $"{label} {stopWatch.ElapsedMilliseconds}";
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/ClientApp.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Fabric;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.ServiceFabric.Services.Communication.AspNetCore;
11 | using Microsoft.ServiceFabric.Services.Communication.Runtime;
12 | using Microsoft.ServiceFabric.Services.Runtime;
13 | using Microsoft.ServiceFabric.Data;
14 |
15 | namespace ClientApp
16 | {
17 | ///
18 | /// The FabricRuntime creates an instance of this class for each service type instance.
19 | ///
20 | internal sealed class ClientApp : StatelessService
21 | {
22 | public ClientApp(StatelessServiceContext context)
23 | : base(context)
24 | { }
25 |
26 | ///
27 | /// Optional override to create listeners (like tcp, http) for this service instance.
28 | ///
29 | /// The collection of listeners.
30 | protected override IEnumerable CreateServiceInstanceListeners()
31 | {
32 | return new ServiceInstanceListener[]
33 | {
34 | new ServiceInstanceListener(serviceContext =>
35 | new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
36 | {
37 | ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");
38 |
39 | return new WebHostBuilder()
40 | .UseKestrel()
41 | .ConfigureServices(
42 | services => services
43 | .AddSingleton(serviceContext))
44 | .UseContentRoot(Directory.GetCurrentDirectory())
45 | .UseStartup()
46 | .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
47 | .UseUrls(url)
48 | .Build();
49 | }))
50 | };
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/ClientApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | InProcess
6 | True
7 | True
8 | win7-x64
9 | False
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/Controllers/CacheDemoController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Caching.Distributed;
8 |
9 | namespace ClientApp.Controllers
10 | {
11 | [Route("api/[controller]")]
12 | [ApiController]
13 | public class CacheDemoController : ControllerBase
14 | {
15 | private readonly IDistributedCache _distributedCache;
16 |
17 | public CacheDemoController(IDistributedCache distributedCache)
18 | {
19 | _distributedCache = distributedCache;
20 | }
21 |
22 | [HttpGet("SetSlidingCacheItem")]
23 | public async Task> SetSlidingCacheItem()
24 | {
25 | var options = new DistributedCacheEntryOptions();
26 | options.SlidingExpiration = TimeSpan.FromSeconds(20);
27 |
28 | await _distributedCache.SetAsync("SlidingCacheItem", Encoding.UTF8.GetBytes(DateTime.Now.ToString()), options);
29 |
30 | return new EmptyResult();
31 | }
32 |
33 | [HttpGet("GetSlidingCacheItem")]
34 | public async Task> GetSlidingCacheItem()
35 | {
36 | var bytes = await _distributedCache.GetAsync("SlidingCacheItem");
37 |
38 | if (bytes != null)
39 | return Content(Encoding.UTF8.GetString(bytes));
40 |
41 | return new EmptyResult();
42 | }
43 |
44 | [HttpGet("SetAbsoluteExpirationCacheItem")]
45 | public async Task> SetAbsoluteExpirationCacheItem()
46 | {
47 | var options = new DistributedCacheEntryOptions();
48 | options.AbsoluteExpiration = DateTime.Now.AddSeconds(20);
49 |
50 | await _distributedCache.SetAsync("AbsoluteExpirationCacheItem", Encoding.UTF8.GetBytes(DateTime.Now.ToString()), options);
51 |
52 | return new EmptyResult();
53 | }
54 |
55 | [HttpGet("GetAbsoluteExpirationCacheItem")]
56 | public async Task> GetAbsoluteExpirationCacheItem()
57 | {
58 | var bytes = await _distributedCache.GetAsync("AbsoluteExpirationCacheItem");
59 |
60 | if (bytes != null)
61 | return Content(Encoding.UTF8.GetString(bytes));
62 |
63 | return new EmptyResult();
64 | }
65 |
66 | [HttpGet("{key}")]
67 | public async Task> Get(string key)
68 | {
69 | var bytes = await _distributedCache.GetAsync(key);
70 |
71 | if(bytes != null)
72 | return Content(Encoding.UTF8.GetString(bytes));
73 |
74 | return new EmptyResult();
75 | }
76 |
77 | [HttpPut("{key}")]
78 | public async Task Put(string key)
79 | {
80 | var request = HttpContext.Request;
81 | using (var reader = new StreamReader(request.Body))
82 | {
83 | var content = await reader.ReadToEndAsync();
84 |
85 | var options = new DistributedCacheEntryOptions();
86 | options.SlidingExpiration = TimeSpan.FromDays(1);
87 | await _distributedCache.SetAsync(key, Encoding.UTF8.GetBytes(content), options);
88 | }
89 | }
90 |
91 | [HttpDelete("{key}")]
92 | public async Task Delete(string key)
93 | {
94 | await _distributedCache.RemoveAsync(key);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/PackageRoot/Config/Settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/PackageRoot/ServiceManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ClientApp.exe
18 | CodePackage
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Services.Runtime;
2 | using System;
3 | using System.Diagnostics;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace ClientApp
8 | {
9 | internal static class Program
10 | {
11 | ///
12 | /// This is the entry point of the service host process.
13 | ///
14 | private static void Main()
15 | {
16 | try
17 | {
18 | // The ServiceManifest.XML file defines one or more service type names.
19 | // Registering a service maps a service type name to a .NET type.
20 | // When Service Fabric creates an instance of this service type,
21 | // an instance of the class is created in this host process.
22 |
23 | ServiceRuntime.RegisterServiceAsync("ClientAppType",
24 | context => new ClientApp(context)).GetAwaiter().GetResult();
25 |
26 | ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(ClientApp).Name);
27 |
28 | // Prevents this host process from terminating so services keeps running.
29 | Thread.Sleep(Timeout.Infinite);
30 | }
31 | catch (Exception e)
32 | {
33 | ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
34 | throw;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/ServiceEventSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.Tracing;
4 | using System.Fabric;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Microsoft.ServiceFabric.Services.Runtime;
9 |
10 | namespace ClientApp
11 | {
12 | [EventSource(Name = "MyCompany-DistributedCache-ClientApp")]
13 | internal sealed class ServiceEventSource : EventSource
14 | {
15 | public static readonly ServiceEventSource Current = new ServiceEventSource();
16 |
17 | static ServiceEventSource()
18 | {
19 | // A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized.
20 | // This problem will be fixed in .NET Framework 4.6.2.
21 | Task.Run(() => { });
22 | }
23 |
24 | // Instance constructor is private to enforce singleton semantics
25 | private ServiceEventSource() : base() { }
26 |
27 | #region Keywords
28 | // Event keywords can be used to categorize events.
29 | // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
30 | // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
31 | public static class Keywords
32 | {
33 | public const EventKeywords Requests = (EventKeywords)0x1L;
34 | public const EventKeywords ServiceInitialization = (EventKeywords)0x2L;
35 | }
36 | #endregion
37 |
38 | #region Events
39 | // Define an instance method for each event you want to record and apply an [Event] attribute to it.
40 | // The method name is the name of the event.
41 | // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
42 | // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
43 | // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
44 | // Put [NonEvent] attribute on all methods that do not define an event.
45 | // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
46 |
47 | [NonEvent]
48 | public void Message(string message, params object[] args)
49 | {
50 | if (this.IsEnabled())
51 | {
52 | string finalMessage = string.Format(message, args);
53 | Message(finalMessage);
54 | }
55 | }
56 |
57 | private const int MessageEventId = 1;
58 | [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
59 | public void Message(string message)
60 | {
61 | if (this.IsEnabled())
62 | {
63 | WriteEvent(MessageEventId, message);
64 | }
65 | }
66 |
67 | [NonEvent]
68 | public void ServiceMessage(ServiceContext serviceContext, string message, params object[] args)
69 | {
70 | if (this.IsEnabled())
71 | {
72 |
73 | string finalMessage = string.Format(message, args);
74 | ServiceMessage(
75 | serviceContext.ServiceName.ToString(),
76 | serviceContext.ServiceTypeName,
77 | GetReplicaOrInstanceId(serviceContext),
78 | serviceContext.PartitionId,
79 | serviceContext.CodePackageActivationContext.ApplicationName,
80 | serviceContext.CodePackageActivationContext.ApplicationTypeName,
81 | serviceContext.NodeContext.NodeName,
82 | finalMessage);
83 | }
84 | }
85 |
86 | // For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
87 | // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
88 | // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
89 | private const int ServiceMessageEventId = 2;
90 | [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")]
91 | private
92 | #if UNSAFE
93 | unsafe
94 | #endif
95 | void ServiceMessage(
96 | string serviceName,
97 | string serviceTypeName,
98 | long replicaOrInstanceId,
99 | Guid partitionId,
100 | string applicationName,
101 | string applicationTypeName,
102 | string nodeName,
103 | string message)
104 | {
105 | #if !UNSAFE
106 | WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message);
107 | #else
108 | const int numArgs = 8;
109 | fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message)
110 | {
111 | EventData* eventData = stackalloc EventData[numArgs];
112 | eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) };
113 | eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) };
114 | eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) };
115 | eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) };
116 | eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) };
117 | eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) };
118 | eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) };
119 | eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) };
120 |
121 | WriteEventCore(ServiceMessageEventId, numArgs, eventData);
122 | }
123 | #endif
124 | }
125 |
126 | private const int ServiceTypeRegisteredEventId = 3;
127 | [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)]
128 | public void ServiceTypeRegistered(int hostProcessId, string serviceType)
129 | {
130 | WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
131 | }
132 |
133 | private const int ServiceHostInitializationFailedEventId = 4;
134 | [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)]
135 | public void ServiceHostInitializationFailed(string exception)
136 | {
137 | WriteEvent(ServiceHostInitializationFailedEventId, exception);
138 | }
139 |
140 | // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity.
141 | // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities,
142 | // and other statistics.
143 | private const int ServiceRequestStartEventId = 5;
144 | [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)]
145 | public void ServiceRequestStart(string requestTypeName)
146 | {
147 | WriteEvent(ServiceRequestStartEventId, requestTypeName);
148 | }
149 |
150 | private const int ServiceRequestStopEventId = 6;
151 | [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)]
152 | public void ServiceRequestStop(string requestTypeName, string exception = "")
153 | {
154 | WriteEvent(ServiceRequestStopEventId, requestTypeName, exception);
155 | }
156 | #endregion
157 |
158 | #region Private methods
159 | private static long GetReplicaOrInstanceId(ServiceContext context)
160 | {
161 | StatelessServiceContext stateless = context as StatelessServiceContext;
162 | if (stateless != null)
163 | {
164 | return stateless.InstanceId;
165 | }
166 |
167 | StatefulServiceContext stateful = context as StatefulServiceContext;
168 | if (stateful != null)
169 | {
170 | return stateful.ReplicaId;
171 | }
172 |
173 | throw new NotSupportedException("Context type not supported.");
174 | }
175 | #if UNSAFE
176 | private int SizeInBytes(string s)
177 | {
178 | if (s == null)
179 | {
180 | return 0;
181 | }
182 | else
183 | {
184 | return (s.Length + 1) * sizeof(char);
185 | }
186 | }
187 | #endif
188 | #endregion
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 | using SoCreate.Extensions.Caching.ServiceFabric;
8 | using System;
9 |
10 | namespace ClientApp
11 | {
12 | public class Startup
13 | {
14 | public Startup(IConfiguration configuration)
15 | {
16 | Configuration = configuration;
17 | }
18 |
19 | public IConfiguration Configuration { get; }
20 |
21 | // This method gets called by the runtime. Use this method to add services to the container.
22 | public void ConfigureServices(IServiceCollection services)
23 | {
24 | services.Configure(o => o.RetryTimeout = TimeSpan.FromSeconds(5));
25 | services.AddDistributedServiceFabricCache();
26 |
27 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
28 | }
29 |
30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
32 | {
33 | if (env.IsDevelopment())
34 | {
35 | app.UseDeveloperExceptionPage();
36 | }
37 |
38 | app.UseRouting();
39 |
40 | app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/Services/ClientApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/DistributedCacheStore.cs:
--------------------------------------------------------------------------------
1 | using System.Fabric;
2 | using SoCreate.Extensions.Caching.ServiceFabric;
3 |
4 | namespace DistributedCacheStore
5 | {
6 | internal sealed partial class DistributedCacheStore : DistributedCacheStoreService
7 | {
8 | public DistributedCacheStore(StatefulServiceContext context)
9 | : base(context, (message) => ServiceEventSource.Current.ServiceMessage(context, message))
10 | { }
11 |
12 | protected override int MaxCacheSizeInMegabytes => 1000;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/DistributedCacheStore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | True
7 | True
8 | win7-x64
9 | False
10 | 7.3
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/PackageRoot/Config/Settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/PackageRoot/ServiceManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | DistributedCacheStore.exe
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.ServiceFabric.Services.Runtime;
6 |
7 | namespace DistributedCacheStore
8 | {
9 | internal static class Program
10 | {
11 | ///
12 | /// This is the entry point of the service host process.
13 | ///
14 | private static void Main()
15 | {
16 | try
17 | {
18 | // The ServiceManifest.XML file defines one or more service type names.
19 | // Registering a service maps a service type name to a .NET type.
20 | // When Service Fabric creates an instance of this service type,
21 | // an instance of the class is created in this host process.
22 |
23 | ServiceRuntime.RegisterServiceAsync("DistributedCacheStoreType",
24 | context => new DistributedCacheStore(context)).GetAwaiter().GetResult();
25 |
26 | ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(DistributedCacheStore).Name);
27 |
28 | // Prevents this host process from terminating so services keep running.
29 | Thread.Sleep(Timeout.Infinite);
30 | }
31 | catch (Exception e)
32 | {
33 | ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
34 | throw;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/Services/DistributedCacheStore/ServiceEventSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.Tracing;
4 | using System.Fabric;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Microsoft.ServiceFabric.Services.Runtime;
9 |
10 | namespace DistributedCacheStore
11 | {
12 | [EventSource(Name = "MyCompany-DistributedCache-DistributedCacheStore")]
13 | internal sealed class ServiceEventSource : EventSource
14 | {
15 | public static readonly ServiceEventSource Current = new ServiceEventSource();
16 |
17 | // Instance constructor is private to enforce singleton semantics
18 | private ServiceEventSource() : base() { }
19 |
20 | #region Keywords
21 | // Event keywords can be used to categorize events.
22 | // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
23 | // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
24 | public static class Keywords
25 | {
26 | public const EventKeywords Requests = (EventKeywords)0x1L;
27 | public const EventKeywords ServiceInitialization = (EventKeywords)0x2L;
28 | }
29 | #endregion
30 |
31 | #region Events
32 | // Define an instance method for each event you want to record and apply an [Event] attribute to it.
33 | // The method name is the name of the event.
34 | // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
35 | // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
36 | // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
37 | // Put [NonEvent] attribute on all methods that do not define an event.
38 | // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
39 |
40 | [NonEvent]
41 | public void Message(string message, params object[] args)
42 | {
43 | if (this.IsEnabled())
44 | {
45 | string finalMessage = string.Format(message, args);
46 | Message(finalMessage);
47 | }
48 | }
49 |
50 | private const int MessageEventId = 1;
51 | [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
52 | public void Message(string message)
53 | {
54 | if (this.IsEnabled())
55 | {
56 | WriteEvent(MessageEventId, message);
57 | }
58 | }
59 |
60 | [NonEvent]
61 | public void ServiceMessage(StatefulServiceContext serviceContext, string message, params object[] args)
62 | {
63 | if (this.IsEnabled())
64 | {
65 | string finalMessage = string.Format(message, args);
66 | ServiceMessage(
67 | serviceContext.ServiceName.ToString(),
68 | serviceContext.ServiceTypeName,
69 | serviceContext.ReplicaId,
70 | serviceContext.PartitionId,
71 | serviceContext.CodePackageActivationContext.ApplicationName,
72 | serviceContext.CodePackageActivationContext.ApplicationTypeName,
73 | serviceContext.NodeContext.NodeName,
74 | finalMessage);
75 | }
76 | }
77 |
78 | // For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
79 | // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
80 | // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
81 | private const int ServiceMessageEventId = 2;
82 | [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")]
83 | private
84 | #if UNSAFE
85 | unsafe
86 | #endif
87 | void ServiceMessage(
88 | string serviceName,
89 | string serviceTypeName,
90 | long replicaOrInstanceId,
91 | Guid partitionId,
92 | string applicationName,
93 | string applicationTypeName,
94 | string nodeName,
95 | string message)
96 | {
97 | #if !UNSAFE
98 | WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message);
99 | #else
100 | const int numArgs = 8;
101 | fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message)
102 | {
103 | EventData* eventData = stackalloc EventData[numArgs];
104 | eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) };
105 | eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) };
106 | eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) };
107 | eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) };
108 | eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) };
109 | eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) };
110 | eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) };
111 | eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) };
112 |
113 | WriteEventCore(ServiceMessageEventId, numArgs, eventData);
114 | }
115 | #endif
116 | }
117 |
118 | private const int ServiceTypeRegisteredEventId = 3;
119 | [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)]
120 | public void ServiceTypeRegistered(int hostProcessId, string serviceType)
121 | {
122 | WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
123 | }
124 |
125 | private const int ServiceHostInitializationFailedEventId = 4;
126 | [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)]
127 | public void ServiceHostInitializationFailed(string exception)
128 | {
129 | WriteEvent(ServiceHostInitializationFailedEventId, exception);
130 | }
131 |
132 | // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity.
133 | // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities,
134 | // and other statistics.
135 | private const int ServiceRequestStartEventId = 5;
136 | [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)]
137 | public void ServiceRequestStart(string requestTypeName)
138 | {
139 | WriteEvent(ServiceRequestStartEventId, requestTypeName);
140 | }
141 |
142 | private const int ServiceRequestStopEventId = 6;
143 | [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)]
144 | public void ServiceRequestStop(string requestTypeName, string exception = "")
145 | {
146 | WriteEvent(ServiceRequestStopEventId, requestTypeName, exception);
147 | }
148 | #endregion
149 |
150 | #region Private methods
151 | #if UNSAFE
152 | private int SizeInBytes(string s)
153 | {
154 | if (s == null)
155 | {
156 | return 0;
157 | }
158 | else
159 | {
160 | return (s.Length + 1) * sizeof(char);
161 | }
162 | }
163 | #endif
164 | #endregion
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/CacheStoreException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SoCreate.Extensions.Caching.ServiceFabric
4 | {
5 | public class CacheStoreException : Exception
6 | {
7 | internal CacheStoreException(string message) : base(message)
8 | {
9 |
10 | }
11 |
12 | internal CacheStoreException(string message, Exception innerException) : base(message, innerException)
13 | {
14 |
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/CacheStoreMetadata.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Data;
2 | using System.IO;
3 |
4 | namespace SoCreate.Extensions.Caching.ServiceFabric
5 | {
6 | public sealed class CacheStoreMetadata
7 | {
8 | public CacheStoreMetadata(int size, string firstCacheKey, string lastCacheKey)
9 | {
10 | Size = size;
11 | FirstCacheKey = firstCacheKey;
12 | LastCacheKey = lastCacheKey;
13 | }
14 |
15 | public int Size { get; private set; }
16 | public string FirstCacheKey { get; private set; }
17 | public string LastCacheKey { get; private set; }
18 | }
19 |
20 | class CacheStoreMetadataSerializer : IStateSerializer
21 | {
22 | CacheStoreMetadata IStateSerializer.Read(BinaryReader reader)
23 | {
24 | return new CacheStoreMetadata(
25 | reader.ReadInt32(),
26 | GetStringValueOrNull(reader.ReadString()),
27 | GetStringValueOrNull(reader.ReadString())
28 | );
29 | }
30 |
31 | void IStateSerializer.Write(CacheStoreMetadata value, BinaryWriter writer)
32 | {
33 | writer.Write(value.Size);
34 | writer.Write(value.FirstCacheKey ?? string.Empty);
35 | writer.Write(value.LastCacheKey ?? string.Empty);
36 | }
37 |
38 | // Read overload for differential de-serialization
39 | CacheStoreMetadata IStateSerializer.Read(CacheStoreMetadata baseValue, BinaryReader reader)
40 | {
41 | return ((IStateSerializer)this).Read(reader);
42 | }
43 |
44 | // Write overload for differential serialization
45 | void IStateSerializer.Write(CacheStoreMetadata baseValue, CacheStoreMetadata newValue, BinaryWriter writer)
46 | {
47 | ((IStateSerializer)this).Write(newValue, writer);
48 | }
49 |
50 | private string GetStringValueOrNull(string value)
51 | {
52 | return value == string.Empty ? null : value;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/CacheStoreNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace SoCreate.Extensions.Caching.ServiceFabric
2 | {
3 | class CacheStoreNotFoundException : CacheStoreException
4 | {
5 | internal CacheStoreNotFoundException(string message) : base(message)
6 | {
7 |
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/CachedItem.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Data;
2 | using System;
3 | using System.IO;
4 |
5 | namespace SoCreate.Extensions.Caching.ServiceFabric
6 | {
7 | public sealed class CachedItem
8 | {
9 | public CachedItem(byte[] value, string beforeCacheKey = null, string afterCacheKey = null, TimeSpan? slidingExpiration = null, DateTimeOffset? absoluteExpiration = null)
10 | {
11 | Value = value;
12 | BeforeCacheKey = beforeCacheKey;
13 | AfterCacheKey = afterCacheKey;
14 | SlidingExpiration = slidingExpiration;
15 | AbsoluteExpiration = absoluteExpiration;
16 | }
17 |
18 | public byte[] Value { get; private set; }
19 | public string BeforeCacheKey { get; private set; }
20 | public string AfterCacheKey { get; private set; }
21 | public TimeSpan? SlidingExpiration { get; private set; }
22 | public DateTimeOffset? AbsoluteExpiration { get; private set; }
23 | }
24 |
25 | class CachedItemSerializer : IStateSerializer
26 | {
27 | CachedItem IStateSerializer.Read(BinaryReader reader)
28 | {
29 | var byteLength = reader.ReadInt32();
30 | return new CachedItem(
31 | reader.ReadBytes(byteLength),
32 | GetStringValueOrNull(reader.ReadString()),
33 | GetStringValueOrNull(reader.ReadString()),
34 | GetTimeSpanFromTicks(reader.ReadInt64()),
35 | GetDateTimeOffsetFromDateData(reader.ReadInt64(), reader.ReadInt64())
36 | );
37 | }
38 |
39 | void IStateSerializer.Write(CachedItem value, BinaryWriter writer)
40 | {
41 | writer.Write(value.Value.Length);
42 | writer.Write(value.Value);
43 | writer.Write(value.BeforeCacheKey ?? string.Empty);
44 | writer.Write(value.AfterCacheKey ?? string.Empty);
45 | writer.Write(GetTicksFromTimeSpan(value.SlidingExpiration));
46 | writer.Write(GetLongDateTimeFromDateTimeOffset(value.AbsoluteExpiration));
47 | writer.Write(GetShortOffsetFromDateTimeOffset(value.AbsoluteExpiration));
48 | }
49 |
50 | // Read overload for differential de-serialization
51 | CachedItem IStateSerializer.Read(CachedItem baseValue, BinaryReader reader)
52 | {
53 | return ((IStateSerializer)this).Read(reader);
54 | }
55 |
56 | // Write overload for differential serialization
57 | void IStateSerializer.Write(CachedItem baseValue, CachedItem newValue, BinaryWriter writer)
58 | {
59 | ((IStateSerializer)this).Write(newValue, writer);
60 | }
61 |
62 | private string GetStringValueOrNull(string value)
63 | {
64 | return value == string.Empty ? null : value;
65 | }
66 |
67 | private TimeSpan? GetTimeSpanFromTicks(long ticks)
68 | {
69 | if (ticks == 0) return null;
70 |
71 | return TimeSpan.FromTicks(ticks);
72 | }
73 |
74 | private long GetTicksFromTimeSpan(TimeSpan? timeSpan)
75 | {
76 | if (!timeSpan.HasValue) return 0;
77 |
78 | return timeSpan.Value.Ticks;
79 | }
80 |
81 | private DateTimeOffset? GetDateTimeOffsetFromDateData(long dateDataTicks, long offsetTicks)
82 | {
83 | return new DateTimeOffset(DateTime.FromBinary(dateDataTicks), new TimeSpan(offsetTicks));
84 | }
85 |
86 | private long GetLongDateTimeFromDateTimeOffset(DateTimeOffset? dateTimeOffset)
87 | {
88 | if (!dateTimeOffset.HasValue) return 0;
89 | return dateTimeOffset.Value.Ticks;
90 | }
91 |
92 | private long GetShortOffsetFromDateTimeOffset(DateTimeOffset? dateTimeOffset)
93 | {
94 | if (!dateTimeOffset.HasValue) return 0;
95 | return dateTimeOffset.Value.Offset.Ticks;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/DistributedCacheStoreLocator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Options;
2 | using Microsoft.ServiceFabric.Services.Client;
3 | using Microsoft.ServiceFabric.Services.Communication.Client;
4 | using Microsoft.ServiceFabric.Services.Remoting.Client;
5 | using Microsoft.ServiceFabric.Services.Remoting.V2;
6 | using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client;
7 | using Microsoft.VisualStudio.Threading;
8 | using System;
9 | using System.Collections.Concurrent;
10 | using System.Fabric;
11 | using System.Fabric.Description;
12 | using System.Fabric.Query;
13 | using System.Linq;
14 | using System.Security.Cryptography;
15 | using System.Text;
16 | using System.Threading.Tasks;
17 |
18 | namespace SoCreate.Extensions.Caching.ServiceFabric
19 | {
20 | class DistributedCacheStoreLocator : IDistributedCacheStoreLocator
21 | {
22 | private const string CacheStoreProperty = "CacheStore";
23 | private const string CacheStorePropertyValue = "true";
24 | private const string ListenerName = "CacheStoreServiceListener";
25 | private AsyncLazy _serviceUri;
26 | private readonly string _endpointName;
27 | private readonly TimeSpan? _retryTimeout;
28 | private readonly FabricClient _fabricClient;
29 | private AsyncLazy _partitionList;
30 | private readonly ConcurrentDictionary _cacheStores;
31 | private readonly ServiceFabricCacheOptions _options;
32 | private readonly IServiceRemotingMessageSerializationProvider _serializationProvider;
33 |
34 | public DistributedCacheStoreLocator(IOptions options)
35 | {
36 | _options = options.Value;
37 | _endpointName = _options.CacheStoreEndpointName ?? ListenerName;
38 | _retryTimeout = _options.RetryTimeout;
39 | _serializationProvider = _options.SerializationProvider;
40 | _fabricClient = new FabricClient();
41 | _cacheStores = new ConcurrentDictionary();
42 | _serviceUri = new AsyncLazy(LocateCacheStoreAsync);
43 | _partitionList = new AsyncLazy(GetPartitionListAsync);
44 | }
45 |
46 | public async Task GetCacheStoreProxy(string cacheKey)
47 | {
48 | var partitionInformation = await GetPartitionInformationForCacheKeyAsync(cacheKey);
49 |
50 | var serviceUri = await _serviceUri.GetValueAsync();
51 |
52 | return _cacheStores.GetOrAdd(partitionInformation.Id, key =>
53 | {
54 | var info = (Int64RangePartitionInformation)partitionInformation;
55 | var resolvedPartition = new ServicePartitionKey(info.LowKey);
56 | var retrySettings = _retryTimeout.HasValue ? new OperationRetrySettings(_retryTimeout.Value) : null;
57 |
58 | var proxyFactory = new ServiceProxyFactory((c) =>
59 | {
60 | return new FabricTransportServiceRemotingClientFactory(serializationProvider: _serializationProvider);
61 | }, retrySettings);
62 |
63 | return proxyFactory.CreateServiceProxy(serviceUri, resolvedPartition, TargetReplicaSelector.Default, _endpointName);
64 | });
65 | }
66 |
67 | private async Task GetPartitionInformationForCacheKeyAsync(string cacheKey)
68 | {
69 | var md5 = MD5.Create();
70 | var value = md5.ComputeHash(Encoding.ASCII.GetBytes(cacheKey));
71 | var key = BitConverter.ToInt64(value, 0);
72 |
73 | var partition = (await _partitionList.GetValueAsync()).Single(p => ((Int64RangePartitionInformation)p.PartitionInformation).LowKey <= key && ((Int64RangePartitionInformation)p.PartitionInformation).HighKey >= key);
74 | return partition.PartitionInformation;
75 | }
76 |
77 | private async Task GetPartitionListAsync()
78 | {
79 | return await _fabricClient.QueryManager.GetPartitionListAsync(await _serviceUri.GetValueAsync());
80 | }
81 |
82 |
83 | private async Task LocateCacheStoreAsync()
84 | {
85 | if (_options.CacheStoreServiceUri != null)
86 | {
87 | return _options.CacheStoreServiceUri;
88 | }
89 | try
90 | {
91 | bool hasPages = true;
92 | var query = new ApplicationQueryDescription() { MaxResults = 50 };
93 |
94 | while (hasPages)
95 | {
96 | var apps = await _fabricClient.QueryManager.GetApplicationPagedListAsync(query);
97 |
98 | query.ContinuationToken = apps.ContinuationToken;
99 |
100 | hasPages = !string.IsNullOrEmpty(query.ContinuationToken);
101 |
102 | foreach (var app in apps)
103 | {
104 | var serviceName = await LocateCacheStoreServiceInApplicationAsync(app.ApplicationName);
105 | if (serviceName != null)
106 | return serviceName;
107 | }
108 | }
109 | }
110 | catch { }
111 |
112 | throw new CacheStoreNotFoundException("Cache store not found in Service Fabric cluster. Try setting the 'CacheStoreServiceUri' configuration option to the location of your cache store."); ;
113 | }
114 |
115 | private async Task LocateCacheStoreServiceInApplicationAsync(Uri applicationName)
116 | {
117 | try
118 | {
119 | bool hasPages = true;
120 | var query = new ServiceQueryDescription(applicationName) { MaxResults = 50 };
121 |
122 | while (hasPages)
123 | {
124 | var services = await _fabricClient.QueryManager.GetServicePagedListAsync(query);
125 |
126 | query.ContinuationToken = services.ContinuationToken;
127 |
128 | hasPages = !string.IsNullOrEmpty(query.ContinuationToken);
129 |
130 | foreach (var service in services)
131 | {
132 | var found = await IsCacheStore(service.ServiceName);
133 | if (found)
134 | return service.ServiceName;
135 | }
136 | }
137 | }
138 | catch { }
139 |
140 | return null;
141 | }
142 |
143 | private async Task IsCacheStore(Uri serviceName)
144 | {
145 | try
146 | {
147 | var isCacheStore = await _fabricClient.PropertyManager.GetPropertyAsync(serviceName, CacheStoreProperty);
148 | return isCacheStore.GetValue() == CacheStorePropertyValue;
149 | }
150 | catch { }
151 |
152 | return false;
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/DistributedCacheStoreService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Internal;
2 | using Microsoft.ServiceFabric.Data;
3 | using Microsoft.ServiceFabric.Data.Collections;
4 | using Microsoft.ServiceFabric.Services.Communication.Runtime;
5 | using Microsoft.ServiceFabric.Services.Remoting.V2;
6 | using Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime;
7 | using Microsoft.ServiceFabric.Services.Runtime;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.Fabric;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 |
14 | namespace SoCreate.Extensions.Caching.ServiceFabric
15 | {
16 | public abstract class DistributedCacheStoreService : StatefulService, IServiceFabricCacheStoreService
17 | {
18 | private const string CacheStoreProperty = "CacheStore";
19 | private const string CacheStorePropertyValue = "true";
20 | const int BytesInMegabyte = 1048576;
21 | const int ByteSizeOffset = 250;
22 | const int DefaultCacheSizeInMegabytes = 100;
23 | const string CacheStoreName = "CacheStore";
24 | const string CacheStoreMetadataName = "CacheStoreMetadata";
25 | const string CacheStoreMetadataKey = "CacheStoreMetadata";
26 | private const string ListenerName = "CacheStoreServiceListener";
27 | private readonly Uri _serviceUri;
28 | private readonly IReliableStateManagerReplica2 _reliableStateManagerReplica;
29 | private readonly Action _log;
30 | private readonly ISystemClock _systemClock;
31 | private int _partitionCount = 1;
32 | protected IServiceRemotingMessageSerializationProvider _serializationProvider;
33 |
34 | public DistributedCacheStoreService(StatefulServiceContext context, Action log = null, IServiceRemotingMessageSerializationProvider serializationProvider = null)
35 | : base(context)
36 | {
37 | _serviceUri = context.ServiceName;
38 | _log = log;
39 | _systemClock = new SystemClock();
40 | _serializationProvider = serializationProvider;
41 |
42 | if (!StateManager.TryAddStateSerializer(new CachedItemSerializer()))
43 | {
44 | throw new InvalidOperationException("Failed to set CachedItem custom serializer");
45 | }
46 |
47 | if (!StateManager.TryAddStateSerializer(new CacheStoreMetadataSerializer()))
48 | {
49 | throw new InvalidOperationException("Failed to set CacheStoreMetadata custom serializer");
50 | }
51 | }
52 |
53 | public DistributedCacheStoreService(StatefulServiceContext context, IReliableStateManagerReplica2 reliableStateManagerReplica, ISystemClock systemClock, Action log)
54 | : base(context, reliableStateManagerReplica)
55 | {
56 | _serviceUri = context.ServiceName;
57 | _reliableStateManagerReplica = reliableStateManagerReplica;
58 | _log = log;
59 | _systemClock = systemClock;
60 | }
61 |
62 | protected async override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken)
63 | {
64 | var client = new FabricClient();
65 | await client.PropertyManager.PutPropertyAsync(_serviceUri, CacheStoreProperty, CacheStorePropertyValue);
66 | _partitionCount = (await client.QueryManager.GetPartitionListAsync(_serviceUri)).Count;
67 | }
68 |
69 | protected virtual int MaxCacheSizeInMegabytes { get { return DefaultCacheSizeInMegabytes; } }
70 |
71 | public async Task GetCachedItemAsync(string key)
72 | {
73 | var cacheStore = await StateManager.GetOrAddAsync>(CacheStoreName);
74 |
75 | var cacheResult = await RetryHelper.ExecuteWithRetry(StateManager, async (tx, cancellationToken, state) =>
76 | {
77 | _log?.Invoke($"Get cached item called with key: {key} on partition id: {Partition?.PartitionInfo.Id}");
78 | return await cacheStore.TryGetValueAsync(tx, key);
79 | });
80 |
81 | if (cacheResult.HasValue)
82 | {
83 | var cachedItem = cacheResult.Value;
84 | var expireTime = cachedItem.AbsoluteExpiration;
85 |
86 | // cache item not expired
87 | if (_systemClock.UtcNow < expireTime)
88 | {
89 | await SetCachedItemAsync(key, cachedItem.Value, cachedItem.SlidingExpiration, cachedItem.AbsoluteExpiration);
90 | return cachedItem.Value;
91 | }
92 | }
93 |
94 | return null;
95 | }
96 |
97 | public async Task SetCachedItemAsync(string key, byte[] value, TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
98 | {
99 | if (slidingExpiration.HasValue)
100 | {
101 | var now = _systemClock.UtcNow;
102 | absoluteExpiration = now.AddMilliseconds(slidingExpiration.Value.TotalMilliseconds);
103 | }
104 |
105 | var cacheStore = await StateManager.GetOrAddAsync>(CacheStoreName);
106 | var cacheStoreMetadata = await StateManager.GetOrAddAsync>(CacheStoreMetadataName);
107 |
108 | await RetryHelper.ExecuteWithRetry(StateManager, async (tx, cancellationToken, state) =>
109 | {
110 | _log?.Invoke($"Set cached item called with key: {key} on partition id: {Partition?.PartitionInfo.Id}");
111 |
112 | Func>> getCacheItem = async (string cacheKey) => await cacheStore.TryGetValueAsync(tx, cacheKey, LockMode.Update);
113 | var linkedDictionaryHelper = new LinkedDictionaryHelper(getCacheItem, ByteSizeOffset);
114 |
115 | var cacheStoreInfo = (await cacheStoreMetadata.TryGetValueAsync(tx, CacheStoreMetadataKey, LockMode.Update)).Value ?? new CacheStoreMetadata(0, null, null);
116 | var existingCacheItem = (await getCacheItem(key)).Value;
117 | var cachedItem = ApplyAbsoluteExpiration(existingCacheItem, absoluteExpiration) ?? new CachedItem(value, null, null, slidingExpiration, absoluteExpiration);
118 |
119 | // empty linked dictionary
120 | if (cacheStoreInfo.FirstCacheKey == null)
121 | {
122 | var metadata = new CacheStoreMetadata(value.Length + ByteSizeOffset, key, key);
123 | await cacheStoreMetadata.SetAsync(tx, CacheStoreMetadataKey, metadata);
124 | await cacheStore.SetAsync(tx, key, cachedItem);
125 | }
126 | else
127 | {
128 | var cacheMetadata = cacheStoreInfo;
129 |
130 | // linked node already exists in dictionary
131 | if (existingCacheItem != null)
132 | {
133 | var removeResult = await linkedDictionaryHelper.Remove(cacheStoreInfo, cachedItem);
134 | cacheMetadata = removeResult.CacheStoreMetadata;
135 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, removeResult);
136 | }
137 |
138 | // add to last
139 | var addLastResult = await linkedDictionaryHelper.AddLast(cacheMetadata, key, cachedItem, value);
140 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, addLastResult);
141 | }
142 | });
143 | }
144 |
145 | public async Task RemoveCachedItemAsync(string key)
146 | {
147 | var cacheStore = await StateManager.GetOrAddAsync>(CacheStoreName);
148 | var cacheStoreMetadata = await StateManager.GetOrAddAsync>(CacheStoreMetadataName);
149 |
150 | await RetryHelper.ExecuteWithRetry(StateManager, async (tx, cancellationToken, state) =>
151 | {
152 | _log?.Invoke($"Remove cached item called with key: {key} on partition id: {Partition?.PartitionInfo.Id}");
153 |
154 | var cacheResult = await cacheStore.TryRemoveAsync(tx, key);
155 | if (cacheResult.HasValue)
156 | {
157 | Func>> getCacheItem = async (string cacheKey) => await cacheStore.TryGetValueAsync(tx, cacheKey, LockMode.Update);
158 | var linkedDictionaryHelper = new LinkedDictionaryHelper(getCacheItem, ByteSizeOffset);
159 |
160 | var cacheStoreInfo = (await cacheStoreMetadata.TryGetValueAsync(tx, CacheStoreMetadataKey, LockMode.Update)).Value ?? new CacheStoreMetadata(0, null, null);
161 | var result = await linkedDictionaryHelper.Remove(cacheStoreInfo, cacheResult.Value);
162 |
163 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, result);
164 | }
165 | });
166 | }
167 |
168 | protected override IEnumerable CreateServiceReplicaListeners()
169 | {
170 | yield return new ServiceReplicaListener(context =>
171 | new FabricTransportServiceRemotingListener(context, this, serializationProvider: _serializationProvider), ListenerName);
172 | }
173 |
174 | protected override async Task RunAsync(CancellationToken cancellationToken)
175 | {
176 | var cacheStore = await StateManager.GetOrAddAsync>(CacheStoreName);
177 | var cacheStoreMetadata = await StateManager.GetOrAddAsync>(CacheStoreMetadataName);
178 |
179 | while (true)
180 | {
181 | await RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize(cancellationToken);
182 | await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken);
183 | }
184 | }
185 |
186 | protected async Task RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize(CancellationToken cancellationToken)
187 | {
188 | var cacheStore = await StateManager.GetOrAddAsync>(CacheStoreName);
189 | var cacheStoreMetadata = await StateManager.GetOrAddAsync>(CacheStoreMetadataName);
190 | bool continueRemovingItems = true;
191 |
192 | while (continueRemovingItems)
193 | {
194 | continueRemovingItems = false;
195 | cancellationToken.ThrowIfCancellationRequested();
196 |
197 | await RetryHelper.ExecuteWithRetry(StateManager, async (tx, cancelToken, state) =>
198 | {
199 | var metadata = await cacheStoreMetadata.TryGetValueAsync(tx, CacheStoreMetadataKey, LockMode.Update);
200 |
201 | if (metadata.HasValue && !string.IsNullOrEmpty(metadata.Value.FirstCacheKey))
202 | {
203 | _log?.Invoke($"Size: {metadata.Value.Size} Max Size: {GetMaxSizeInBytes()}");
204 |
205 | if (metadata.Value.Size > GetMaxSizeInBytes())
206 | {
207 | Func>> getCacheItem = async (string cacheKey) => await cacheStore.TryGetValueAsync(tx, cacheKey, LockMode.Update);
208 | var linkedDictionaryHelper = new LinkedDictionaryHelper(getCacheItem, ByteSizeOffset);
209 |
210 | var firstItemKey = metadata.Value.FirstCacheKey;
211 |
212 | var firstCachedItem = (await getCacheItem(firstItemKey)).Value;
213 |
214 | if (firstCachedItem != null)
215 | {
216 | // Move item to last item if cached item is not expired
217 | if (firstCachedItem.AbsoluteExpiration > _systemClock.UtcNow)
218 | {
219 | // remove cached item
220 | var removeResult = await linkedDictionaryHelper.Remove(metadata.Value, firstCachedItem);
221 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, removeResult);
222 |
223 | // add to last
224 | var addLastResult = await linkedDictionaryHelper.AddLast(removeResult.CacheStoreMetadata, firstItemKey, firstCachedItem, firstCachedItem.Value);
225 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, addLastResult);
226 |
227 | continueRemovingItems = addLastResult.CacheStoreMetadata.Size > GetMaxSizeInBytes();
228 | }
229 | else // Remove
230 | {
231 | _log?.Invoke($"Auto Removing: {metadata.Value.FirstCacheKey}");
232 |
233 | var result = await linkedDictionaryHelper.Remove(metadata.Value, firstCachedItem);
234 | await ApplyChanges(tx, cacheStore, cacheStoreMetadata, result);
235 | await cacheStore.TryRemoveAsync(tx, metadata.Value.FirstCacheKey);
236 |
237 | continueRemovingItems = result.CacheStoreMetadata.Size > GetMaxSizeInBytes();
238 | }
239 | }
240 | }
241 | }
242 | });
243 | await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
244 | }
245 | }
246 |
247 | private int GetMaxSizeInBytes()
248 | {
249 | return (MaxCacheSizeInMegabytes * BytesInMegabyte) / _partitionCount;
250 | }
251 |
252 | private async Task ApplyChanges(ITransaction tx, IReliableDictionary cachedItemStore, IReliableDictionary cacheStoreMetadata, LinkedDictionaryItemsChanged linkedDictionaryItemsChanged)
253 | {
254 | foreach (var cacheItem in linkedDictionaryItemsChanged.CachedItemsToUpdate)
255 | {
256 | await cachedItemStore.SetAsync(tx, cacheItem.Key, cacheItem.Value);
257 | }
258 |
259 | await cacheStoreMetadata.SetAsync(tx, CacheStoreMetadataKey, linkedDictionaryItemsChanged.CacheStoreMetadata);
260 | }
261 |
262 | private CachedItem ApplyAbsoluteExpiration(CachedItem cachedItem, DateTimeOffset? absoluteExpiration)
263 | {
264 | if (cachedItem != null)
265 | {
266 | return new CachedItem(cachedItem.Value, cachedItem.BeforeCacheKey, cachedItem.AfterCacheKey, cachedItem.SlidingExpiration, absoluteExpiration);
267 | }
268 | return null;
269 | }
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/IDistributedCacheStoreLocator.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace SoCreate.Extensions.Caching.ServiceFabric
4 | {
5 | interface IDistributedCacheStoreLocator
6 | {
7 | Task GetCacheStoreProxy(string cacheKey);
8 | }
9 | }
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/IServiceFabricCacheStoreService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Services.Remoting;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace SoCreate.Extensions.Caching.ServiceFabric
6 | {
7 | public interface IServiceFabricCacheStoreService : IService
8 | {
9 | Task GetCachedItemAsync(string key);
10 | Task SetCachedItemAsync(string key, byte[] value, TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration);
11 | Task RemoveCachedItemAsync(string key);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/LinkedDictionaryHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Data;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 |
6 | namespace SoCreate.Extensions.Caching.ServiceFabric
7 | {
8 | class LinkedDictionaryHelper
9 | {
10 | private readonly Func>> _getCacheItem;
11 | private readonly int _byteSizeOffset;
12 |
13 | public LinkedDictionaryHelper(Func>> getCacheItem) : this(getCacheItem, 0)
14 | {
15 | }
16 |
17 | public LinkedDictionaryHelper(Func>> getCacheItem, int byteSizeOffset)
18 | {
19 | _getCacheItem = getCacheItem;
20 | _byteSizeOffset = byteSizeOffset;
21 | }
22 |
23 | public async Task Remove(CacheStoreMetadata cacheStoreMetadata, CachedItem cachedItem)
24 | {
25 | var before = cachedItem.BeforeCacheKey;
26 | var after = cachedItem.AfterCacheKey;
27 | var size = (cacheStoreMetadata.Size - cachedItem.Value.Length) - _byteSizeOffset;
28 |
29 | // only item in linked dictionary
30 | if (before == null && after == null)
31 | {
32 | return new LinkedDictionaryItemsChanged(new Dictionary(), new CacheStoreMetadata(size, null, null));
33 | }
34 |
35 | // first item in linked dictionary
36 | if (before == null)
37 | {
38 | var afterCachedItem = (await _getCacheItem(after)).Value;
39 | var newCachedItem = new Dictionary { { after, new CachedItem(afterCachedItem.Value, null, afterCachedItem.AfterCacheKey, afterCachedItem.SlidingExpiration, afterCachedItem.AbsoluteExpiration) } };
40 | return new LinkedDictionaryItemsChanged(newCachedItem, new CacheStoreMetadata(size, after, cacheStoreMetadata.LastCacheKey));
41 | }
42 |
43 | // last item in linked dictionary
44 | if (after == null)
45 | {
46 | var beforeCachedItem = (await _getCacheItem(before)).Value;
47 | var newCachedItem = new Dictionary { { before, new CachedItem(beforeCachedItem.Value, beforeCachedItem.BeforeCacheKey, null, beforeCachedItem.SlidingExpiration, beforeCachedItem.AbsoluteExpiration) } };
48 | return new LinkedDictionaryItemsChanged(newCachedItem, new CacheStoreMetadata(size, cacheStoreMetadata.FirstCacheKey, before));
49 | }
50 |
51 | // middle item in linked dictionary
52 |
53 | var beforeItem = (await _getCacheItem(before)).Value;
54 | var afterItem = (await _getCacheItem(after)).Value;
55 |
56 | var metadata = new CacheStoreMetadata(size, cacheStoreMetadata.FirstCacheKey, cacheStoreMetadata.LastCacheKey);
57 |
58 | var newCachedItems = new Dictionary();
59 | // add new before cached item
60 | newCachedItems.Add(before, new CachedItem(beforeItem.Value, beforeItem.BeforeCacheKey, after, beforeItem.SlidingExpiration, beforeItem.AbsoluteExpiration));
61 | // add new after cached item
62 | newCachedItems.Add(after, new CachedItem(afterItem.Value, before, afterItem.AfterCacheKey, afterItem.SlidingExpiration, afterItem.AbsoluteExpiration));
63 |
64 | return new LinkedDictionaryItemsChanged(newCachedItems, metadata);
65 | }
66 |
67 | public async Task AddLast(CacheStoreMetadata cacheStoreMetadata, string cacheItemKey, CachedItem cachedItem, byte[] newValue)
68 | {
69 | var cachedDictionary = new Dictionary();
70 | var firstCacheKey = cacheItemKey;
71 |
72 | // set current last item to be the second from last
73 | if (cacheStoreMetadata.LastCacheKey != null)
74 | {
75 | var currentLastCacheItem = (await _getCacheItem(cacheStoreMetadata.LastCacheKey)).Value;
76 | firstCacheKey = cacheStoreMetadata.FirstCacheKey;
77 | cachedDictionary.Add(cacheStoreMetadata.LastCacheKey, new CachedItem(currentLastCacheItem.Value, currentLastCacheItem.BeforeCacheKey, cacheItemKey, currentLastCacheItem.SlidingExpiration, currentLastCacheItem.AbsoluteExpiration));
78 | }
79 |
80 | // set new cached item to be last item in list
81 | cachedDictionary.Add(cacheItemKey, new CachedItem(newValue, cacheStoreMetadata.LastCacheKey, null, cachedItem.SlidingExpiration, cachedItem.AbsoluteExpiration));
82 |
83 | // calculate size of new collection
84 | var size = (cacheStoreMetadata.Size + newValue.Length) + _byteSizeOffset;
85 |
86 | // set new last item in the metadata
87 | var newCacheStoreMetadata = new CacheStoreMetadata(size, firstCacheKey, cacheItemKey);
88 |
89 | return new LinkedDictionaryItemsChanged(cachedDictionary, newCacheStoreMetadata);
90 | }
91 | }
92 |
93 | class LinkedDictionaryItemsChanged
94 | {
95 | public LinkedDictionaryItemsChanged(Dictionary cachedItemsToUpdate, CacheStoreMetadata cacheStoreMetadata)
96 | {
97 | CachedItemsToUpdate = cachedItemsToUpdate;
98 | CacheStoreMetadata = cacheStoreMetadata;
99 | }
100 |
101 | public IReadOnlyDictionary CachedItemsToUpdate { get; private set; }
102 | public CacheStoreMetadata CacheStoreMetadata { get; private set; }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/RetryHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceFabric.Data;
2 | using System;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace SoCreate.Extensions.Caching.ServiceFabric
7 | {
8 | static class RetryHelper
9 | {
10 | private const int DefaultMaxAttempts = 10;
11 | private static readonly TimeSpan InitialDelay = TimeSpan.FromMilliseconds(200);
12 | private static readonly TimeSpan MinimumDelay = TimeSpan.FromMilliseconds(200);
13 |
14 | public static async Task ExecuteWithRetry(
15 | IReliableStateManager stateManager,
16 | Func> operation,
17 | object state = null,
18 | CancellationToken cancellationToken = default(CancellationToken),
19 | int maxAttempts = DefaultMaxAttempts,
20 | TimeSpan? initialDelay = null)
21 | {
22 | if (stateManager == null) throw new ArgumentNullException(nameof(stateManager));
23 | if (operation == null) throw new ArgumentNullException(nameof(operation));
24 | if (maxAttempts <= 0) maxAttempts = DefaultMaxAttempts;
25 | if (initialDelay == null || initialDelay.Value < MinimumDelay)
26 | initialDelay = InitialDelay;
27 |
28 | Func> wrapped = async (token, st) =>
29 | {
30 | TResult result;
31 | using (var tran = stateManager.CreateTransaction())
32 | {
33 | try
34 | {
35 | result = await operation(tran, cancellationToken, state);
36 | await tran.CommitAsync();
37 | }
38 | catch (TimeoutException)
39 | {
40 | tran.Abort();
41 | throw;
42 | }
43 | }
44 | return result;
45 | };
46 |
47 | var outerResult = await ExecuteWithRetry(wrapped, state, cancellationToken, maxAttempts, initialDelay);
48 | return outerResult;
49 | }
50 |
51 | public static async Task ExecuteWithRetry(
52 | IReliableStateManager stateManager,
53 | Func operation,
54 | object state = null,
55 | CancellationToken cancellationToken = default(CancellationToken),
56 | int maxAttempts = DefaultMaxAttempts,
57 | TimeSpan? initialDelay = null)
58 | {
59 | if (stateManager == null) throw new ArgumentNullException(nameof(stateManager));
60 | if (operation == null) throw new ArgumentNullException(nameof(operation));
61 | if (maxAttempts <= 0) maxAttempts = DefaultMaxAttempts;
62 | if (initialDelay == null || initialDelay.Value < MinimumDelay)
63 | initialDelay = InitialDelay;
64 |
65 | Func> wrapped = async (token, st) =>
66 | {
67 | using (var tran = stateManager.CreateTransaction())
68 | {
69 | try
70 | {
71 | await operation(tran, cancellationToken, state);
72 | await tran.CommitAsync();
73 | }
74 | catch (TimeoutException)
75 | {
76 | tran.Abort();
77 | throw;
78 | }
79 | }
80 | return null;
81 | };
82 |
83 | await ExecuteWithRetry(wrapped, state, cancellationToken, maxAttempts, initialDelay);
84 | }
85 |
86 | public static async Task ExecuteWithRetry(
87 | Func> operation,
88 | object state = null,
89 | CancellationToken cancellationToken = default(CancellationToken),
90 | int maxAttempts = DefaultMaxAttempts,
91 | TimeSpan? initialDelay = null)
92 | {
93 | if (operation == null) throw new ArgumentNullException(nameof(operation));
94 | if (maxAttempts <= 0) maxAttempts = DefaultMaxAttempts;
95 | if (initialDelay == null || initialDelay.Value < MinimumDelay)
96 | initialDelay = InitialDelay;
97 |
98 | var result = default(TResult);
99 | for (int attempts = 0; attempts < maxAttempts; attempts++)
100 | {
101 | try
102 | {
103 | result = await operation(cancellationToken, state);
104 | break;
105 | }
106 | catch (TimeoutException)
107 | {
108 | if (attempts == DefaultMaxAttempts)
109 | {
110 | throw;
111 | }
112 | }
113 |
114 | //exponential back-off
115 | int factor = (int)Math.Pow(2, attempts) + 1;
116 | int delay = new Random(Guid.NewGuid().GetHashCode()).Next((int)(initialDelay.Value.TotalMilliseconds * 0.5D), (int)(initialDelay.Value.TotalMilliseconds * 1.5D));
117 | await Task.Delay(factor * delay, cancellationToken);
118 | }
119 | return result;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/ServiceFabricCacheOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Options;
2 | using Microsoft.ServiceFabric.Services.Remoting.V2;
3 | using System;
4 |
5 | namespace SoCreate.Extensions.Caching.ServiceFabric
6 | {
7 | public class ServiceFabricCacheOptions : IOptions
8 | {
9 | public ServiceFabricCacheOptions Value => this;
10 |
11 | public Uri CacheStoreServiceUri { get; set; }
12 | public string CacheStoreEndpointName { get; set; }
13 | public Guid CacheStoreId { get; set; }
14 | public TimeSpan? RetryTimeout { get; set; }
15 | public IServiceRemotingMessageSerializationProvider SerializationProvider { get; set; }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/ServiceFabricCachingServicesExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Caching.Distributed;
2 | using Microsoft.Extensions.Internal;
3 | using SoCreate.Extensions.Caching.ServiceFabric;
4 | using System;
5 |
6 | namespace Microsoft.Extensions.DependencyInjection
7 | {
8 | public static class ServiceFabricCachingServicesExtensions
9 | {
10 | public static IServiceCollection AddDistributedServiceFabricCache(this IServiceCollection services, Action setupAction = null)
11 | {
12 | if (services == null) throw new ArgumentNullException(nameof(services));
13 |
14 | if (setupAction == null) {
15 | setupAction = (s) => { };
16 | }
17 |
18 | services.AddOptions();
19 | services.Configure(setupAction);
20 |
21 | return services
22 | .AddSingleton()
23 | .AddSingleton()
24 | .AddSingleton();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/ServiceFabricDistributedCache.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Caching.Distributed;
2 | using Microsoft.Extensions.Internal;
3 | using Microsoft.Extensions.Options;
4 | using System;
5 | using System.Runtime.CompilerServices;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | [assembly: InternalsVisibleTo("SoCreate.Extensions.Caching.Tests")]
10 | namespace SoCreate.Extensions.Caching.ServiceFabric
11 | {
12 | class ServiceFabricDistributedCache : IDistributedCache
13 | {
14 | private const string CacheStoreExceptionMessage = "An exception occurred while accessing cache store.";
15 | private readonly IDistributedCacheStoreLocator _distributedCacheStoreLocator;
16 | private readonly ISystemClock _systemClock;
17 | private readonly Guid _cacheStoreId;
18 |
19 | public ServiceFabricDistributedCache(IOptions options, IDistributedCacheStoreLocator distributedCacheStoreLocator, ISystemClock systemClock)
20 | {
21 | _cacheStoreId = options.Value.CacheStoreId;
22 | _distributedCacheStoreLocator = distributedCacheStoreLocator;
23 | _systemClock = systemClock;
24 | }
25 |
26 | public byte[] Get(string key)
27 | {
28 | return GetAsync(key).Result;
29 | }
30 |
31 | public async Task GetAsync(string key, CancellationToken token = default(CancellationToken))
32 | {
33 | if (key == null) throw new ArgumentNullException(nameof(key));
34 |
35 | key = FormatCacheKey(key);
36 | var proxy = await _distributedCacheStoreLocator.GetCacheStoreProxy(key).ConfigureAwait(false);
37 |
38 | return await ExecuteWithExceptionWrapping(async () => await proxy.GetCachedItemAsync(key).ConfigureAwait(false));
39 | }
40 |
41 | public void Refresh(string key)
42 | {
43 | RefreshAsync(key).Wait();
44 | }
45 |
46 | public async Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
47 | {
48 | if (key == null) throw new ArgumentNullException(nameof(key));
49 |
50 | await GetAsync(key, token);
51 | }
52 |
53 | public void Remove(string key)
54 | {
55 | RemoveAsync(key).Wait();
56 | }
57 |
58 | public async Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
59 | {
60 | if (key == null) throw new ArgumentNullException(nameof(key));
61 |
62 | key = FormatCacheKey(key);
63 | var proxy = await _distributedCacheStoreLocator.GetCacheStoreProxy(key).ConfigureAwait(false);
64 | await ExecuteWithExceptionWrapping(async () => await proxy.RemoveCachedItemAsync(key).ConfigureAwait(false));
65 | }
66 |
67 | public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
68 | {
69 | SetAsync(key, value, options).Wait();
70 | }
71 |
72 | public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
73 | {
74 | if (key == null) throw new ArgumentNullException(nameof(key));
75 | if (value == null) throw new ArgumentNullException(nameof(value));
76 |
77 | var absoluteExpireTime = GetAbsoluteExpiration(_systemClock.UtcNow, options);
78 | ValidateOptions(options.SlidingExpiration, absoluteExpireTime);
79 |
80 | key = FormatCacheKey(key);
81 | var proxy = await _distributedCacheStoreLocator.GetCacheStoreProxy(key).ConfigureAwait(false);
82 | await ExecuteWithExceptionWrapping(async () => await proxy.SetCachedItemAsync(key, value, options.SlidingExpiration, absoluteExpireTime).ConfigureAwait(false));
83 | }
84 |
85 | private DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset utcNow, DistributedCacheEntryOptions options)
86 | {
87 | var expireTime = new DateTimeOffset?();
88 | if (options.AbsoluteExpirationRelativeToNow.HasValue)
89 | expireTime = new DateTimeOffset?(utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value));
90 | else if (options.AbsoluteExpiration.HasValue)
91 | {
92 | if (options.AbsoluteExpiration.Value <= utcNow)
93 | throw new InvalidOperationException("The absolute expiration value must be in the future.");
94 | expireTime = new DateTimeOffset?(options.AbsoluteExpiration.Value);
95 | }
96 | return expireTime;
97 | }
98 |
99 | private void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
100 | {
101 | if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)
102 | throw new InvalidOperationException("Either absolute or sliding expiration needs to be provided.");
103 | }
104 |
105 | private string FormatCacheKey(string key)
106 | {
107 | return $"{_cacheStoreId}-{key}";
108 | }
109 |
110 | private static async Task ExecuteWithExceptionWrapping(Func callback)
111 | {
112 | try
113 | {
114 | await callback().ConfigureAwait(false);
115 | }
116 | catch (Exception exception)
117 | {
118 | throw new CacheStoreException(CacheStoreExceptionMessage, exception);
119 | }
120 | }
121 |
122 | private static async Task ExecuteWithExceptionWrapping(Func> callback)
123 | {
124 | try
125 | {
126 | return await callback().ConfigureAwait(false);
127 | }
128 | catch (Exception exception)
129 | {
130 | throw new CacheStoreException(CacheStoreExceptionMessage, exception);
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/SoCreate.Extensions.Caching.ServiceFabric/SoCreate.Extensions.Caching.ServiceFabric.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | Jami Lurock
7 | SoCreate
8 | true
9 | https://raw.githubusercontent.com/SoCreate/service-fabric-distributed-cache/master/LICENSE
10 | An implementation of the IDistributedCache that uses a Stateful Reliable Service Fabric service to act as the cache store. You can use this library to setup a distributed cache and use Service Fabric instead of Redis or SQL Server.
11 | https://github.com/SoCreate/service-fabric-distributed-cache
12 | Cache DistributedCache ServiceFabric
13 | http://service-fabric-distributed-cache.socreate.it/
14 | git
15 | © SoCreate. All rights reserved.
16 | 3.1.0
17 | https://raw.githubusercontent.com/SoCreate/service-fabric-distributed-cache/master/assets/icon-64x64.png
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/SoCreate.Extensions.Caching.Tests/AutoMoqDataAttribute.cs:
--------------------------------------------------------------------------------
1 | using AutoFixture;
2 | using AutoFixture.AutoMoq;
3 | using AutoFixture.Xunit2;
4 | using ServiceFabric.Mocks;
5 |
6 | namespace SoCreate.Extensions.Caching.Tests
7 | {
8 | public class AutoMoqDataAttribute : AutoDataAttribute
9 | {
10 | public AutoMoqDataAttribute() : base(() =>
11 | {
12 | var fixture = new Fixture().Customize(new AutoMoqCustomization {GenerateDelegates = true});
13 | fixture.Register(() => MockStatefulServiceContextFactory.Default);
14 | return fixture;
15 | })
16 | {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/SoCreate.Extensions.Caching.Tests/DistributedCacheStoreServiceTest.cs:
--------------------------------------------------------------------------------
1 | using AutoFixture.Xunit2;
2 | using Microsoft.Extensions.Internal;
3 | using Microsoft.ServiceFabric.Data;
4 | using Microsoft.ServiceFabric.Data.Collections;
5 | using Moq;
6 | using SoCreate.Extensions.Caching.ServiceFabric;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Fabric;
10 | using System.Text;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using Xunit;
14 |
15 | namespace SoCreate.Extensions.Caching.Tests
16 | {
17 | public class DistributedCacheStoreServiceTest
18 | {
19 | [Theory, AutoMoqData]
20 | async void GetCachedItemAsync_GetItemThatExistsWithSlidingExpiration_ItemIsMovedToLastItem(
21 | [Frozen]Mock stateManager,
22 | [Frozen]Mock> cacheItemDict,
23 | [Frozen]Mock> metadataDict,
24 | [Frozen]Mock systemClock,
25 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
26 | {
27 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
28 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
29 |
30 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
31 |
32 | SetupInMemoryStores(stateManager, cacheItemDict);
33 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
34 |
35 | await cacheStore.SetCachedItemAsync("mykey1", cacheValue, TimeSpan.FromSeconds(10), null);
36 | await cacheStore.SetCachedItemAsync("mykey2", cacheValue, TimeSpan.FromSeconds(10), null);
37 | await cacheStore.SetCachedItemAsync("mykey3", cacheValue, TimeSpan.FromSeconds(10), null);
38 |
39 | Assert.Equal("mykey3", metadata["CacheStoreMetadata"].LastCacheKey);
40 |
41 | await cacheStore.GetCachedItemAsync("mykey2");
42 |
43 | Assert.Equal("mykey2", metadata["CacheStoreMetadata"].LastCacheKey);
44 | }
45 |
46 | [Theory, AutoMoqData]
47 | async void GetCachedItemAsync_GetItemThatExistsWithAbsoluteExpiration_ItemIsMovedToLastItem(
48 | [Frozen]Mock stateManager,
49 | [Frozen]Mock> cacheItemDict,
50 | [Frozen]Mock> metadataDict,
51 | [Frozen]Mock systemClock,
52 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
53 | {
54 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
55 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
56 | var expireTime = currentTime.AddSeconds(30);
57 |
58 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
59 |
60 | SetupInMemoryStores(stateManager, cacheItemDict);
61 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
62 |
63 | await cacheStore.SetCachedItemAsync("mykey1", cacheValue, null, expireTime);
64 | await cacheStore.SetCachedItemAsync("mykey2", cacheValue, null, expireTime);
65 | await cacheStore.SetCachedItemAsync("mykey3", cacheValue, null, expireTime);
66 |
67 | Assert.Equal("mykey3", metadata["CacheStoreMetadata"].LastCacheKey);
68 |
69 | await cacheStore.GetCachedItemAsync("mykey2");
70 |
71 | Assert.Equal("mykey2", metadata["CacheStoreMetadata"].LastCacheKey);
72 | }
73 |
74 | [Theory, AutoMoqData]
75 | async void GetCachedItemAsync_GetItemThatDoesNotExist_NullResultReturned(
76 | [Frozen]Mock stateManager,
77 | [Frozen]Mock> cacheItemDict,
78 | [Frozen]Mock> metadataDict,
79 | [Frozen]Mock systemClock,
80 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
81 | {
82 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
83 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
84 | var expireTime = currentTime.AddSeconds(1);
85 |
86 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
87 |
88 | SetupInMemoryStores(stateManager, metadataDict);
89 | SetupInMemoryStores(stateManager, cacheItemDict);
90 |
91 | var result = await cacheStore.GetCachedItemAsync("keyThatDoesNotExist");
92 | Assert.Null(result);
93 | }
94 |
95 | [Theory, AutoMoqData]
96 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsNotAbsoluteExpired_CachedItemReturned(
97 | [Frozen]Mock stateManager,
98 | [Frozen]Mock> cacheItemDict,
99 | [Frozen]Mock> metadataDict,
100 | [Frozen]Mock systemClock,
101 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
102 | {
103 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
104 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
105 | var expireTime = currentTime.AddSeconds(1);
106 |
107 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
108 |
109 | SetupInMemoryStores(stateManager, metadataDict);
110 | SetupInMemoryStores(stateManager, cacheItemDict);
111 |
112 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, null, expireTime);
113 | var result = await cacheStore.GetCachedItemAsync("mykey");
114 | Assert.Equal(cacheValue, result);
115 | }
116 |
117 | [Theory, AutoMoqData]
118 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsAbsoluteExpired_NullResultReturned(
119 | [Frozen]Mock stateManager,
120 | [Frozen]Mock> cacheItemDict,
121 | [Frozen]Mock> metadataDict,
122 | [Frozen]Mock systemClock,
123 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
124 | {
125 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
126 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
127 | var expireTime = currentTime.AddSeconds(-1);
128 |
129 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
130 |
131 | SetupInMemoryStores(stateManager, metadataDict);
132 | SetupInMemoryStores(stateManager, cacheItemDict);
133 |
134 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, null, expireTime);
135 | var result = await cacheStore.GetCachedItemAsync("mykey");
136 | Assert.Null(result);
137 | }
138 |
139 | [Theory, AutoMoqData]
140 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsAbsoluteExpiredDoesNotSlideTime_ExpireTimeDoesNotSlide(
141 | [Frozen]Mock stateManager,
142 | [Frozen]Mock> cacheItemDict,
143 | [Frozen]Mock> metadataDict,
144 | [Frozen]Mock systemClock,
145 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
146 | {
147 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
148 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
149 | var expireTime = currentTime.AddSeconds(5);
150 |
151 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
152 |
153 | SetupInMemoryStores(stateManager, metadataDict);
154 | SetupInMemoryStores(stateManager, cacheItemDict);
155 |
156 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, null, expireTime);
157 | var result = await cacheStore.GetCachedItemAsync("mykey");
158 | Assert.Equal(cacheValue, result);
159 |
160 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(5));
161 |
162 | var resultAfter6Seconds = await cacheStore.GetCachedItemAsync("mykey");
163 | Assert.Null(resultAfter6Seconds);
164 | }
165 |
166 | [Theory, AutoMoqData]
167 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsNotSlidingExpired_CachedItemReturned(
168 | [Frozen]Mock stateManager,
169 | [Frozen]Mock> cacheItemDict,
170 | [Frozen]Mock> metadataDict,
171 | [Frozen]Mock systemClock,
172 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
173 | {
174 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
175 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
176 |
177 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
178 |
179 | SetupInMemoryStores(stateManager, metadataDict);
180 | SetupInMemoryStores(stateManager, cacheItemDict);
181 |
182 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, TimeSpan.FromSeconds(1), null);
183 | var result = await cacheStore.GetCachedItemAsync("mykey");
184 | Assert.Equal(cacheValue, result);
185 | }
186 |
187 |
188 | [Theory, AutoMoqData]
189 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsSlidingExpired_NullResultReturned(
190 | [Frozen]Mock stateManager,
191 | [Frozen]Mock> cacheItemDict,
192 | [Frozen]Mock> metadataDict,
193 | [Frozen]Mock systemClock,
194 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
195 | {
196 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
197 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
198 |
199 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
200 |
201 | SetupInMemoryStores(stateManager, metadataDict);
202 | SetupInMemoryStores(stateManager, cacheItemDict);
203 |
204 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, TimeSpan.FromSeconds(1), null);
205 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(2));
206 | var result = await cacheStore.GetCachedItemAsync("mykey");
207 | Assert.Null(result);
208 | }
209 |
210 | [Theory, AutoMoqData]
211 | async void GetCachedItemAsync_GetItemThatDoesHaveKeyAndIsIsSlidingExpired_SlidedExpirationUpdates(
212 | [Frozen]Mock stateManager,
213 | [Frozen]Mock> cacheItemDict,
214 | [Frozen]Mock> metadataDict,
215 | [Frozen]Mock systemClock,
216 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
217 | {
218 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
219 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
220 |
221 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
222 |
223 | SetupInMemoryStores(stateManager, cacheItemDict);
224 | SetupInMemoryStores(stateManager, metadataDict);
225 |
226 | await cacheStore.SetCachedItemAsync("mykey", cacheValue, TimeSpan.FromSeconds(10), null);
227 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(5));
228 | var resultAfter5Seconds = await cacheStore.GetCachedItemAsync("mykey");
229 | Assert.Equal(cacheValue, resultAfter5Seconds);
230 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(8));
231 | var resultAfter8Seconds = await cacheStore.GetCachedItemAsync("mykey");
232 | Assert.Equal(cacheValue, resultAfter8Seconds);
233 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(9));
234 | var resultAfter9Seconds = await cacheStore.GetCachedItemAsync("mykey");
235 | Assert.Equal(cacheValue, resultAfter9Seconds);
236 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(19));
237 | var resultAfter19Seconds = await cacheStore.GetCachedItemAsync("mykey");
238 | Assert.Null(resultAfter19Seconds);
239 | }
240 |
241 | [Theory, AutoMoqData]
242 | async void SetCachedItemAsync_AddItemsToCreateLinkedDictionary_DictionaryCreatedWithItemsLinked(
243 | [Frozen]Mock stateManager,
244 | [Frozen]Mock> cacheItemDict,
245 | [Frozen]Mock> metadataDict,
246 | [Frozen]Mock systemClock,
247 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
248 | {
249 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
250 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
251 |
252 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
253 |
254 | var cachedItems = SetupInMemoryStores(stateManager, cacheItemDict);
255 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
256 |
257 | await cacheStore.SetCachedItemAsync("1", cacheValue, TimeSpan.FromSeconds(10), null);
258 | await cacheStore.SetCachedItemAsync("2", cacheValue, TimeSpan.FromSeconds(10), null);
259 | await cacheStore.SetCachedItemAsync("3", cacheValue, TimeSpan.FromSeconds(10), null);
260 | await cacheStore.SetCachedItemAsync("4", cacheValue, TimeSpan.FromSeconds(10), null);
261 |
262 | Assert.Null(cachedItems["1"].BeforeCacheKey);
263 | foreach (var item in cachedItems)
264 | {
265 | if (item.Value.BeforeCacheKey != null)
266 | {
267 | Assert.Equal(item.Key, cachedItems[item.Value.BeforeCacheKey].AfterCacheKey);
268 | }
269 | if (item.Value.AfterCacheKey != null)
270 | {
271 | Assert.Equal(item.Key, cachedItems[item.Value.AfterCacheKey].BeforeCacheKey);
272 | }
273 | }
274 | Assert.Null(cachedItems["4"].AfterCacheKey);
275 |
276 | Assert.Equal("1", metadata["CacheStoreMetadata"].FirstCacheKey);
277 | Assert.Equal("4", metadata["CacheStoreMetadata"].LastCacheKey);
278 | Assert.Equal((cacheValue.Length + 250) * cachedItems.Count, metadata["CacheStoreMetadata"].Size);
279 | }
280 |
281 | [Theory, AutoMoqData]
282 | async void RemoveCachedItemAsync_RemoveItemsFromLinkedDictionary_ListStaysLinkedTogetherAfterItemsRemoved(
283 | [Frozen]Mock stateManager,
284 | [Frozen]Mock> cacheItemDict,
285 | [Frozen]Mock> metadataDict,
286 | [Frozen]Mock systemClock,
287 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
288 | {
289 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
290 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
291 |
292 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
293 |
294 | var cachedItems = SetupInMemoryStores(stateManager, cacheItemDict);
295 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
296 |
297 | await cacheStore.SetCachedItemAsync("1", cacheValue, TimeSpan.FromSeconds(10), null);
298 | await cacheStore.SetCachedItemAsync("2", cacheValue, TimeSpan.FromSeconds(10), null);
299 | await cacheStore.SetCachedItemAsync("3", cacheValue, TimeSpan.FromSeconds(10), null);
300 | await cacheStore.SetCachedItemAsync("4", cacheValue, TimeSpan.FromSeconds(10), null);
301 | await cacheStore.SetCachedItemAsync("5", cacheValue, TimeSpan.FromSeconds(10), null);
302 | await cacheStore.SetCachedItemAsync("6", cacheValue, TimeSpan.FromSeconds(10), null);
303 | await cacheStore.SetCachedItemAsync("7", cacheValue, TimeSpan.FromSeconds(10), null);
304 | await cacheStore.SetCachedItemAsync("8", cacheValue, TimeSpan.FromSeconds(10), null);
305 |
306 | await cacheStore.RemoveCachedItemAsync("3");
307 | await cacheStore.RemoveCachedItemAsync("4");
308 | await cacheStore.RemoveCachedItemAsync("8");
309 | await cacheStore.RemoveCachedItemAsync("1");
310 |
311 | Assert.Null(cachedItems["2"].BeforeCacheKey);
312 | foreach (var item in cachedItems)
313 | {
314 | if (item.Value.BeforeCacheKey != null)
315 | {
316 | Assert.Equal(item.Key, cachedItems[item.Value.BeforeCacheKey].AfterCacheKey);
317 | }
318 | if (item.Value.AfterCacheKey != null)
319 | {
320 | Assert.Equal(item.Key, cachedItems[item.Value.AfterCacheKey].BeforeCacheKey);
321 | }
322 | }
323 | Assert.Null(cachedItems["7"].AfterCacheKey);
324 |
325 | Assert.Equal("2", metadata["CacheStoreMetadata"].FirstCacheKey);
326 | Assert.Equal("7", metadata["CacheStoreMetadata"].LastCacheKey);
327 | Assert.Equal((cacheValue.Length + 250) * cachedItems.Count, metadata["CacheStoreMetadata"].Size);
328 | }
329 |
330 | [Theory, AutoMoqData]
331 | async void RemoveCachedItemAsync_RemoveItemsFromLinkedDictionary_RemovalWorksWithMalformedMetadata(
332 | [Frozen]Mock stateManager,
333 | [Frozen]Mock> cacheItemDict,
334 | [Frozen]Mock> metadataDict,
335 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
336 | {
337 | var cacheValue = Encoding.UTF8.GetBytes("someValue");
338 |
339 | var cachedItems = SetupInMemoryStores(stateManager, cacheItemDict);
340 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
341 |
342 | await cacheStore.SetCachedItemAsync("1", cacheValue, TimeSpan.FromSeconds(10), null);
343 | await cacheStore.SetCachedItemAsync("2", cacheValue, TimeSpan.FromSeconds(10), null);
344 | await cacheStore.SetCachedItemAsync("3", cacheValue, TimeSpan.FromSeconds(10), null);
345 |
346 | metadata["CacheStoreMetadata"] = new CacheStoreMetadata(int.MaxValue, null, "3");
347 | await cacheStore.RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize();
348 |
349 | metadata["CacheStoreMetadata"] = new CacheStoreMetadata(int.MaxValue, "Garbage", "3");
350 | await cacheStore.RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize();
351 | }
352 |
353 | [Theory, AutoMoqData]
354 | async void RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize_RemoveItemsFromLinkedDictionary_DoesNotRemoveNonExpiredItems(
355 | [Frozen]Mock stateManager,
356 | [Frozen]Mock> cacheItemDict,
357 | [Frozen]Mock> metadataDict,
358 | [Frozen]Mock systemClock,
359 | [Greedy]ServiceFabricDistributedCacheStoreService cacheStore)
360 | {
361 | var cacheValue = new byte[1000000];
362 | var currentTime = new DateTime(2019, 2, 1, 1, 0, 0);
363 |
364 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime);
365 |
366 | var cachedItems = SetupInMemoryStores(stateManager, cacheItemDict);
367 | var metadata = SetupInMemoryStores(stateManager, metadataDict);
368 |
369 | await cacheStore.SetCachedItemAsync("1", cacheValue, TimeSpan.FromMinutes(10), null);
370 | for (var i = 2; i <= 10; i++)
371 | {
372 | await cacheStore.SetCachedItemAsync(i.ToString(), cacheValue, TimeSpan.FromSeconds(10), null);
373 | }
374 |
375 | systemClock.SetupGet(m => m.UtcNow).Returns(currentTime.AddSeconds(10));
376 | await cacheStore.RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize();
377 |
378 | Assert.Single(cachedItems);
379 | Assert.Equal("1", metadata["CacheStoreMetadata"].FirstCacheKey);
380 | Assert.Equal("1", metadata["CacheStoreMetadata"].LastCacheKey);
381 | }
382 |
383 |
384 | private Dictionary SetupInMemoryStores(Mock stateManager, Mock> reliableDict) where TKey : IComparable, IEquatable
385 | {
386 | var inMemoryDict = new Dictionary();
387 | Func> getItem = (key) => inMemoryDict.ContainsKey(key) ? new ConditionalValue(true, inMemoryDict[key]) : new ConditionalValue(false, default(TValue));
388 |
389 | stateManager.Setup(m => m.GetOrAddAsync>(It.IsAny())).Returns(Task.FromResult(reliableDict.Object));
390 | reliableDict.Setup(m => m.TryGetValueAsync(It.IsAny(), It.IsAny())).Returns((ITransaction t, TKey key) => Task.FromResult(getItem(key)));
391 | reliableDict.Setup(m => m.TryGetValueAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((ITransaction t, TKey key, LockMode l) => Task.FromResult(getItem(key)));
392 | reliableDict.Setup(m => m.SetAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns((ITransaction t, TKey key, TValue ci) => { inMemoryDict[key] = ci; return Task.CompletedTask; });
393 | reliableDict.Setup(m => m.TryRemoveAsync(It.IsAny(), It.IsAny())).Returns((ITransaction t, TKey key) => { var r = getItem(key); inMemoryDict.Remove(key); return Task.FromResult(r); });
394 |
395 | return inMemoryDict;
396 | }
397 |
398 | class ServiceFabricDistributedCacheStoreService : DistributedCacheStoreService
399 | {
400 | public ServiceFabricDistributedCacheStoreService(StatefulServiceContext context, IReliableStateManagerReplica2 replica, ISystemClock clock) : base(context, replica, clock, (m) => { })
401 | {
402 | }
403 |
404 | protected override int MaxCacheSizeInMegabytes => 1;
405 |
406 | public async Task RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize()
407 | {
408 | await RemoveLeastRecentlyUsedCacheItemWhenOverMaxCacheSize(CancellationToken.None);
409 | }
410 | }
411 | }
412 | }
413 |
--------------------------------------------------------------------------------
/tests/SoCreate.Extensions.Caching.Tests/LinkedDictionaryHelperTest.cs:
--------------------------------------------------------------------------------
1 | using AutoFixture.Xunit2;
2 | using Microsoft.ServiceFabric.Data;
3 | using Moq;
4 | using SoCreate.Extensions.Caching.ServiceFabric;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Xunit;
10 |
11 | namespace SoCreate.Extensions.Caching.Tests
12 | {
13 | public class LinkedDictionaryHelperTest
14 | {
15 | [Theory, AutoMoqData]
16 | async void AddLast_AddNewItemToEndOfList_LinkOldLastItemToNewLastItem(
17 | [Frozen]Mock>>> getCacheItem,
18 | CacheStoreMetadata cacheStoreMetadata,
19 | ConditionalValue cachedItem,
20 | CachedItem newCachedItem,
21 | LinkedDictionaryHelper linkedDictionaryHelper)
22 | {
23 | var newItemKey = "NewLastItem";
24 | var cachedValue = Encoding.UTF8.GetBytes("some value");
25 | var totalSize = cacheStoreMetadata.Size + cachedValue.Length;
26 |
27 | getCacheItem.Setup(mock => mock(It.IsAny())).ReturnsAsync(await Task.FromResult(cachedItem));
28 |
29 | var result = await linkedDictionaryHelper.AddLast(cacheStoreMetadata, newItemKey, newCachedItem, cachedValue);
30 | Assert.Equal(2, result.CachedItemsToUpdate.Count);
31 |
32 | var oldLastItem = result.CachedItemsToUpdate[result.CachedItemsToUpdate[newItemKey].BeforeCacheKey];
33 | Assert.Equal(newItemKey, oldLastItem.AfterCacheKey);
34 | Assert.Equal(cachedItem.Value.Value, oldLastItem.Value);
35 | Assert.Equal(cachedItem.Value.SlidingExpiration, oldLastItem.SlidingExpiration);
36 | Assert.Equal(cachedItem.Value.AbsoluteExpiration, oldLastItem.AbsoluteExpiration);
37 |
38 | var newLastItem = result.CachedItemsToUpdate[newItemKey];
39 |
40 | Assert.Equal(cacheStoreMetadata.LastCacheKey, newLastItem.BeforeCacheKey);
41 | Assert.Null(newLastItem.AfterCacheKey);
42 | Assert.Equal(cachedValue, newLastItem.Value);
43 | Assert.Equal(newCachedItem.SlidingExpiration, newLastItem.SlidingExpiration);
44 | Assert.Equal(newCachedItem.AbsoluteExpiration, newLastItem.AbsoluteExpiration);
45 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
46 | Assert.Equal(cacheStoreMetadata.FirstCacheKey, result.CacheStoreMetadata.FirstCacheKey);
47 | Assert.Equal(newItemKey, result.CacheStoreMetadata.LastCacheKey);
48 | }
49 |
50 | [Theory, AutoMoqData]
51 | async void AddLast_AddNewItemToEndOfEmptyList_ListContainsOnlyNewItem(
52 | [Frozen]Mock>>> getCacheItem,
53 | ConditionalValue cachedItem,
54 | CachedItem newCachedItem,
55 | LinkedDictionaryHelper linkedDictionaryHelper)
56 | {
57 | var cacheStoreMetadata = new CacheStoreMetadata(0, null, null);
58 | var newItemKey = "NewLastItem";
59 | var cachedValue = Encoding.UTF8.GetBytes("some value");
60 | var totalSize = cacheStoreMetadata.Size + cachedValue.Length;
61 |
62 | getCacheItem.Setup(mock => mock(It.IsAny())).ReturnsAsync(await Task.FromResult(cachedItem));
63 |
64 | var result = await linkedDictionaryHelper.AddLast(cacheStoreMetadata, newItemKey, newCachedItem, cachedValue);
65 | Assert.Equal(1, result.CachedItemsToUpdate.Count);
66 |
67 | var newLastItem = result.CachedItemsToUpdate[newItemKey];
68 |
69 | Assert.Null(newLastItem.BeforeCacheKey);
70 | Assert.Null(newLastItem.AfterCacheKey);
71 | Assert.Equal(cachedValue, newLastItem.Value);
72 | Assert.Equal(newCachedItem.SlidingExpiration, newLastItem.SlidingExpiration);
73 | Assert.Equal(newCachedItem.AbsoluteExpiration, newLastItem.AbsoluteExpiration);
74 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
75 | Assert.Equal(newItemKey, result.CacheStoreMetadata.FirstCacheKey);
76 | Assert.Equal(newItemKey, result.CacheStoreMetadata.LastCacheKey);
77 | }
78 |
79 | [Theory, AutoMoqData]
80 | async void Remove_OnlyItemInLinkedDictionary_SetCacheItemNotCalled(
81 | CacheStoreMetadata cacheStoreMetadata,
82 | LinkedDictionaryHelper linkedDictionaryHelper)
83 | {
84 | var cachedValue = Encoding.UTF8.GetBytes("some value");
85 | var totalSize = cacheStoreMetadata.Size - cachedValue.Length;
86 | var c = new CachedItem(cachedValue, null, null);
87 |
88 | var result = await linkedDictionaryHelper.Remove(cacheStoreMetadata, c);
89 | Assert.Empty(result.CachedItemsToUpdate);
90 |
91 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
92 | Assert.Null(result.CacheStoreMetadata.FirstCacheKey);
93 | Assert.Null(result.CacheStoreMetadata.LastCacheKey);
94 | }
95 |
96 | [Theory, AutoMoqData]
97 | async void Remove_FirstItemInLinkedDictionary_SetSecondItemToBeFirst(
98 | [Frozen]Mock>>> getCacheItem,
99 | CacheStoreMetadata cacheStoreMetadata,
100 | LinkedDictionaryHelper linkedDictionaryHelper)
101 | {
102 | var cachedValue = Encoding.UTF8.GetBytes("some value");
103 | var totalSize = cacheStoreMetadata.Size - cachedValue.Length;
104 |
105 | var items = new Dictionary {
106 | { "1", new CachedItem(cachedValue, null, "2") },
107 | { "2", new CachedItem(cachedValue, "1", "3", TimeSpan.FromMilliseconds(100), DateTimeOffset.MinValue) },
108 | { "3", new CachedItem(cachedValue, "2", null) }
109 | };
110 |
111 | getCacheItem.Setup(mock => mock(It.IsAny())).ReturnsAsync(await Task.FromResult(new ConditionalValue(true, items["2"])));
112 |
113 | var result = await linkedDictionaryHelper.Remove(cacheStoreMetadata, items["1"]);
114 | Assert.Single(result.CachedItemsToUpdate);
115 | var newFirstItem = result.CachedItemsToUpdate["2"];
116 |
117 | Assert.Null(newFirstItem.BeforeCacheKey);
118 | Assert.Equal(items["2"].AfterCacheKey, newFirstItem.AfterCacheKey);
119 | Assert.Equal(cachedValue, newFirstItem.Value);
120 | Assert.Equal(items["2"].SlidingExpiration, newFirstItem.SlidingExpiration);
121 | Assert.Equal(items["2"].AbsoluteExpiration, newFirstItem.AbsoluteExpiration);
122 |
123 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
124 | Assert.Equal("2", result.CacheStoreMetadata.FirstCacheKey);
125 | Assert.Equal(cacheStoreMetadata.LastCacheKey, result.CacheStoreMetadata.LastCacheKey);
126 | }
127 |
128 | [Theory, AutoMoqData]
129 | async void Remove_LastItemInLinkedDictionary_SetSecondItemFromLastToBeLast(
130 | [Frozen]Mock>>> getCacheItem,
131 | CacheStoreMetadata cacheStoreMetadata,
132 | LinkedDictionaryHelper linkedDictionaryHelper)
133 | {
134 | var cachedValue = Encoding.UTF8.GetBytes("some value");
135 | var totalSize = cacheStoreMetadata.Size - cachedValue.Length;
136 |
137 | var items = new Dictionary {
138 | { "1", new CachedItem(cachedValue, null, "2") },
139 | { "2", new CachedItem(cachedValue, "1", "3", TimeSpan.FromMilliseconds(100), DateTimeOffset.MinValue) },
140 | { "3", new CachedItem(cachedValue, "2", null) }
141 | };
142 |
143 | getCacheItem.Setup(mock => mock(It.IsAny())).ReturnsAsync(await Task.FromResult(new ConditionalValue(true, items["2"])));
144 |
145 | var result = await linkedDictionaryHelper.Remove(cacheStoreMetadata, items["3"]);
146 | Assert.Single(result.CachedItemsToUpdate);
147 | var newLastItem = result.CachedItemsToUpdate["2"];
148 |
149 | Assert.Equal(items["2"].BeforeCacheKey, newLastItem.BeforeCacheKey);
150 | Assert.Null(newLastItem.AfterCacheKey);
151 | Assert.Equal(cachedValue, newLastItem.Value);
152 | Assert.Equal(items["2"].SlidingExpiration, newLastItem.SlidingExpiration);
153 | Assert.Equal(items["2"].AbsoluteExpiration, newLastItem.AbsoluteExpiration);
154 |
155 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
156 | Assert.Equal(cacheStoreMetadata.FirstCacheKey, result.CacheStoreMetadata.FirstCacheKey);
157 | Assert.Equal("2", result.CacheStoreMetadata.LastCacheKey);
158 | }
159 |
160 | [Theory, AutoMoqData]
161 | async void Remove_MiddleItemInLinkedDictionary_ItemBeforeAndAfterNeedTobeLinked(
162 | [Frozen]Mock>>> getCacheItem,
163 | CacheStoreMetadata cacheStoreMetadata,
164 | LinkedDictionaryHelper linkedDictionaryHelper)
165 | {
166 | var cachedValue = Encoding.UTF8.GetBytes("some value");
167 | var totalSize = cacheStoreMetadata.Size - cachedValue.Length;
168 |
169 | var items = new Dictionary {
170 | { "1", new CachedItem(cachedValue, null, "2", TimeSpan.FromMilliseconds(10), DateTimeOffset.MinValue) },
171 | { "2", new CachedItem(cachedValue, "1", "3") },
172 | { "3", new CachedItem(cachedValue, "2", null, TimeSpan.FromMilliseconds(100), DateTimeOffset.MinValue) }
173 | };
174 |
175 | getCacheItem.Setup(mock => mock("1")).ReturnsAsync(await Task.FromResult(new ConditionalValue(true, items["1"])));
176 | getCacheItem.Setup(mock => mock("3")).ReturnsAsync(await Task.FromResult(new ConditionalValue(true, items["3"])));
177 |
178 |
179 | var result = await linkedDictionaryHelper.Remove(cacheStoreMetadata, items["2"]);
180 | Assert.Equal(2, result.CachedItemsToUpdate.Count);
181 |
182 | var newFirstItem = result.CachedItemsToUpdate["1"];
183 | Assert.Null(newFirstItem.BeforeCacheKey);
184 | Assert.Equal("3", newFirstItem.AfterCacheKey);
185 | Assert.Equal(cachedValue, newFirstItem.Value);
186 | Assert.Equal(items["1"].SlidingExpiration, newFirstItem.SlidingExpiration);
187 | Assert.Equal(items["1"].AbsoluteExpiration, newFirstItem.AbsoluteExpiration);
188 |
189 | var newLastItem = result.CachedItemsToUpdate["3"];
190 | Assert.Equal("1", newLastItem.BeforeCacheKey);
191 | Assert.Null(newLastItem.AfterCacheKey);
192 | Assert.Equal(cachedValue, newLastItem.Value);
193 | Assert.Equal(items["3"].SlidingExpiration, newLastItem.SlidingExpiration);
194 | Assert.Equal(items["3"].AbsoluteExpiration, newLastItem.AbsoluteExpiration);
195 |
196 | Assert.Equal(totalSize, result.CacheStoreMetadata.Size);
197 | Assert.Equal(cacheStoreMetadata.FirstCacheKey, result.CacheStoreMetadata.FirstCacheKey);
198 | Assert.Equal(cacheStoreMetadata.LastCacheKey, result.CacheStoreMetadata.LastCacheKey);
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/tests/SoCreate.Extensions.Caching.Tests/SoCreate.Extensions.Caching.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | win7-x64
6 |
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------