1 /* $NetBSD: rf_sstf.c,v 1.11 2004/03/04 01:57:54 oster Exp $ */
2 /*
3 * Copyright (c) 1995 Carnegie-Mellon University.
4 * All rights reserved.
5 *
6 * Author: Jim Zelenka
7 *
8 * Permission to use, copy, modify and distribute this software and
9 * its documentation is hereby granted, provided that both the copyright
10 * notice and this permission notice appear in all copies of the
11 * software, derivative works or modified versions, and any portions
12 * thereof, and that both notices appear in supporting documentation.
13 *
14 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
15 * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
16 * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
17 *
18 * Carnegie Mellon requests users of this software to return to
19 *
20 * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
21 * School of Computer Science
22 * Carnegie Mellon University
23 * Pittsburgh PA 15213-3890
24 *
25 * any improvements or extensions that they make and grant Carnegie the
26 * rights to redistribute these changes.
27 */
28
29 /*******************************************************************************
30 *
31 * sstf.c -- prioritized shortest seek time first disk queueing code
32 *
33 ******************************************************************************/
34
35 #include <sys/cdefs.h>
36 __KERNEL_RCSID(0, "$NetBSD: rf_sstf.c,v 1.11 2004/03/04 01:57:54 oster Exp $");
37
38 #include <dev/raidframe/raidframevar.h>
39
40 #include "rf_alloclist.h"
41 #include "rf_stripelocks.h"
42 #include "rf_layout.h"
43 #include "rf_diskqueue.h"
44 #include "rf_sstf.h"
45 #include "rf_debugMem.h"
46 #include "rf_general.h"
47 #include "rf_options.h"
48 #include "rf_raid.h"
49
50 #define DIR_LEFT 1
51 #define DIR_RIGHT 2
52 #define DIR_EITHER 3
53
54 #define SNUM_DIFF(_a_,_b_) (((_a_)>(_b_))?((_a_)-(_b_)):((_b_)-(_a_)))
55
56 #define QSUM(_sstfq_) (((_sstfq_)->lopri.qlen)+((_sstfq_)->left.qlen)+((_sstfq_)->right.qlen))
57
58
59 static void
60 do_sstf_ord_q(RF_DiskQueueData_t **,
61 RF_DiskQueueData_t **,
62 RF_DiskQueueData_t *);
63
64 static RF_DiskQueueData_t *
65 closest_to_arm(RF_SstfQ_t *,
66 RF_SectorNum_t,
67 int *,
68 int);
69 static void do_dequeue(RF_SstfQ_t *, RF_DiskQueueData_t *);
70
71
72 static void
73 do_sstf_ord_q(queuep, tailp, req)
74 RF_DiskQueueData_t **queuep;
75 RF_DiskQueueData_t **tailp;
76 RF_DiskQueueData_t *req;
77 {
78 RF_DiskQueueData_t *r, *s;
79
80 if (*queuep == NULL) {
81 *queuep = req;
82 *tailp = req;
83 req->next = NULL;
84 req->prev = NULL;
85 return;
86 }
87 if (req->sectorOffset <= (*queuep)->sectorOffset) {
88 req->next = *queuep;
89 req->prev = NULL;
90 (*queuep)->prev = req;
91 *queuep = req;
92 return;
93 }
94 if (req->sectorOffset > (*tailp)->sectorOffset) {
95 /* optimization */
96 r = NULL;
97 s = *tailp;
98 goto q_at_end;
99 }
100 for (s = NULL, r = *queuep; r; s = r, r = r->next) {
101 if (r->sectorOffset >= req->sectorOffset) {
102 /* insert after s, before r */
103 RF_ASSERT(s);
104 req->next = r;
105 r->prev = req;
106 s->next = req;
107 req->prev = s;
108 return;
109 }
110 }
111 q_at_end:
112 /* insert after s, at end of queue */
113 RF_ASSERT(r == NULL);
114 RF_ASSERT(s);
115 RF_ASSERT(s == (*tailp));
116 req->next = NULL;
117 req->prev = s;
118 s->next = req;
119 *tailp = req;
120 }
121 /* for removing from head-of-queue */
122 #define DO_HEAD_DEQ(_r_,_q_) { \
123 _r_ = (_q_)->queue; \
124 RF_ASSERT((_r_) != NULL); \
125 (_q_)->queue = (_r_)->next; \
126 (_q_)->qlen--; \
127 if ((_q_)->qlen == 0) { \
128 RF_ASSERT((_r_) == (_q_)->qtail); \
129 RF_ASSERT((_q_)->queue == NULL); \
130 (_q_)->qtail = NULL; \
131 } \
132 else { \
133 RF_ASSERT((_q_)->queue->prev == (_r_)); \
134 (_q_)->queue->prev = NULL; \
135 } \
136 }
137
138 /* for removing from end-of-queue */
139 #define DO_TAIL_DEQ(_r_,_q_) { \
140 _r_ = (_q_)->qtail; \
141 RF_ASSERT((_r_) != NULL); \
142 (_q_)->qtail = (_r_)->prev; \
143 (_q_)->qlen--; \
144 if ((_q_)->qlen == 0) { \
145 RF_ASSERT((_r_) == (_q_)->queue); \
146 RF_ASSERT((_q_)->qtail == NULL); \
147 (_q_)->queue = NULL; \
148 } \
149 else { \
150 RF_ASSERT((_q_)->qtail->next == (_r_)); \
151 (_q_)->qtail->next = NULL; \
152 } \
153 }
154
155 #define DO_BEST_DEQ(_l_,_r_,_q_) { \
156 if (SNUM_DIFF((_q_)->queue->sectorOffset,_l_) \
157 < SNUM_DIFF((_q_)->qtail->sectorOffset,_l_)) \
158 { \
159 DO_HEAD_DEQ(_r_,_q_); \
160 } \
161 else { \
162 DO_TAIL_DEQ(_r_,_q_); \
163 } \
164 }
165
166 static RF_DiskQueueData_t *
167 closest_to_arm(queue, arm_pos, dir, allow_reverse)
168 RF_SstfQ_t *queue;
169 RF_SectorNum_t arm_pos;
170 int *dir;
171 int allow_reverse;
172 {
173 RF_SectorNum_t best_pos_l = 0, this_pos_l = 0, last_pos = 0;
174 RF_SectorNum_t best_pos_r = 0, this_pos_r = 0;
175 RF_DiskQueueData_t *r, *best_l, *best_r;
176
177 best_r = best_l = NULL;
178 for (r = queue->queue; r; r = r->next) {
179 if (r->sectorOffset < arm_pos) {
180 if (best_l == NULL) {
181 best_l = r;
182 last_pos = best_pos_l = this_pos_l;
183 } else {
184 this_pos_l = arm_pos - r->sectorOffset;
185 if (this_pos_l < best_pos_l) {
186 best_l = r;
187 last_pos = best_pos_l = this_pos_l;
188 } else {
189 last_pos = this_pos_l;
190 }
191 }
192 } else {
193 if (best_r == NULL) {
194 best_r = r;
195 last_pos = best_pos_r = this_pos_r;
196 } else {
197 this_pos_r = r->sectorOffset - arm_pos;
198 if (this_pos_r < best_pos_r) {
199 best_r = r;
200 last_pos = best_pos_r = this_pos_r;
201 } else {
202 last_pos = this_pos_r;
203 }
204 if (this_pos_r > last_pos) {
205 /* getting farther away */
206 break;
207 }
208 }
209 }
210 }
211 if ((best_r == NULL) && (best_l == NULL))
212 return (NULL);
213 if ((*dir == DIR_RIGHT) && best_r)
214 return (best_r);
215 if ((*dir == DIR_LEFT) && best_l)
216 return (best_l);
217 if (*dir == DIR_EITHER) {
218 if (best_l == NULL)
219 return (best_r);
220 if (best_r == NULL)
221 return (best_l);
222 if (best_pos_r < best_pos_l)
223 return (best_r);
224 else
225 return (best_l);
226 }
227 /*
228 * Nothing in the direction we want to go. Reverse or
229 * reset the arm. We know we have an I/O in the other
230 * direction.
231 */
232 if (allow_reverse) {
233 if (*dir == DIR_RIGHT) {
234 *dir = DIR_LEFT;
235 return (best_l);
236 } else {
237 *dir = DIR_RIGHT;
238 return (best_r);
239 }
240 }
241 /*
242 * Reset (beginning of queue).
243 */
244 RF_ASSERT(*dir == DIR_RIGHT);
245 return (queue->queue);
246 }
247
248 void *
249 rf_SstfCreate(sect_per_disk, cl_list, listp)
250 RF_SectorCount_t sect_per_disk;
251 RF_AllocListElem_t *cl_list;
252 RF_ShutdownList_t **listp;
253 {
254 RF_Sstf_t *sstfq;
255
256 RF_MallocAndAdd(sstfq, sizeof(RF_Sstf_t), (RF_Sstf_t *), cl_list);
257 sstfq->dir = DIR_EITHER;
258 sstfq->allow_reverse = 1;
259 return ((void *) sstfq);
260 }
261
262 void *
263 rf_ScanCreate(sect_per_disk, cl_list, listp)
264 RF_SectorCount_t sect_per_disk;
265 RF_AllocListElem_t *cl_list;
266 RF_ShutdownList_t **listp;
267 {
268 RF_Sstf_t *scanq;
269
270 RF_MallocAndAdd(scanq, sizeof(RF_Sstf_t), (RF_Sstf_t *), cl_list);
271 scanq->dir = DIR_RIGHT;
272 scanq->allow_reverse = 1;
273 return ((void *) scanq);
274 }
275
276 void *
277 rf_CscanCreate(sect_per_disk, cl_list, listp)
278 RF_SectorCount_t sect_per_disk;
279 RF_AllocListElem_t *cl_list;
280 RF_ShutdownList_t **listp;
281 {
282 RF_Sstf_t *cscanq;
283
284 RF_MallocAndAdd(cscanq, sizeof(RF_Sstf_t), (RF_Sstf_t *), cl_list);
285 cscanq->dir = DIR_RIGHT;
286 return ((void *) cscanq);
287 }
288
289 void
290 rf_SstfEnqueue(qptr, req, priority)
291 void *qptr;
292 RF_DiskQueueData_t *req;
293 int priority;
294 {
295 RF_Sstf_t *sstfq;
296
297 sstfq = (RF_Sstf_t *) qptr;
298
299 if (priority == RF_IO_LOW_PRIORITY) {
300 #if RF_DEBUG_QUEUE
301 if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
302 RF_DiskQueue_t *dq;
303 dq = (RF_DiskQueue_t *) req->queue;
304 printf("raid%d: ENQ lopri %d queues are %d,%d,%d\n",
305 req->raidPtr->raidid,
306 dq->col,
307 sstfq->left.qlen, sstfq->right.qlen,
308 sstfq->lopri.qlen);
309 }
310 #endif
311 do_sstf_ord_q(&sstfq->lopri.queue, &sstfq->lopri.qtail, req);
312 sstfq->lopri.qlen++;
313 } else {
314 if (req->sectorOffset < sstfq->last_sector) {
315 do_sstf_ord_q(&sstfq->left.queue, &sstfq->left.qtail, req);
316 sstfq->left.qlen++;
317 } else {
318 do_sstf_ord_q(&sstfq->right.queue, &sstfq->right.qtail, req);
319 sstfq->right.qlen++;
320 }
321 }
322 }
323
324 static void
325 do_dequeue(queue, req)
326 RF_SstfQ_t *queue;
327 RF_DiskQueueData_t *req;
328 {
329 RF_DiskQueueData_t *req2;
330
331 #if RF_DEBUG_QUEUE
332 if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
333 printf("raid%d: do_dequeue\n", req->raidPtr->raidid);
334 }
335 #endif
336 if (req == queue->queue) {
337 DO_HEAD_DEQ(req2, queue);
338 RF_ASSERT(req2 == req);
339 } else
340 if (req == queue->qtail) {
341 DO_TAIL_DEQ(req2, queue);
342 RF_ASSERT(req2 == req);
343 } else {
344 /* dequeue from middle of list */
345 RF_ASSERT(req->next);
346 RF_ASSERT(req->prev);
347 queue->qlen--;
348 req->next->prev = req->prev;
349 req->prev->next = req->next;
350 req->next = req->prev = NULL;
351 }
352 }
353
354 RF_DiskQueueData_t *
355 rf_SstfDequeue(qptr)
356 void *qptr;
357 {
358 RF_DiskQueueData_t *req = NULL;
359 RF_Sstf_t *sstfq;
360
361 sstfq = (RF_Sstf_t *) qptr;
362
363 #if RF_DEBUG_QUEUE
364 if (rf_sstfDebug) {
365 RF_DiskQueue_t *dq;
366 dq = (RF_DiskQueue_t *) req->queue;
367 RF_ASSERT(QSUM(sstfq) == dq->queueLength);
368 printf("raid%d: sstf: Dequeue %d queues are %d,%d,%d\n",
369 req->raidPtr->raidid, dq->col,
370 sstfq->left.qlen, sstfq->right.qlen, sstfq->lopri.qlen);
371 }
372 #endif
373 if (sstfq->left.queue == NULL) {
374 RF_ASSERT(sstfq->left.qlen == 0);
375 if (sstfq->right.queue == NULL) {
376 RF_ASSERT(sstfq->right.qlen == 0);
377 if (sstfq->lopri.queue == NULL) {
378 RF_ASSERT(sstfq->lopri.qlen == 0);
379 return (NULL);
380 }
381 #if RF_DEBUG_QUEUE
382 if (rf_sstfDebug) {
383 printf("raid%d: sstf: check for close lopri",
384 req->raidPtr->raidid);
385 }
386 #endif
387 req = closest_to_arm(&sstfq->lopri, sstfq->last_sector,
388 &sstfq->dir, sstfq->allow_reverse);
389 #if RF_DEBUG_QUEUE
390 if (rf_sstfDebug) {
391 printf("raid%d: sstf: closest_to_arm said %lx",
392 req->raidPtr->raidid, (long) req);
393 }
394 #endif
395 if (req == NULL)
396 return (NULL);
397 do_dequeue(&sstfq->lopri, req);
398 } else {
399 DO_BEST_DEQ(sstfq->last_sector, req, &sstfq->right);
400 }
401 } else {
402 if (sstfq->right.queue == NULL) {
403 RF_ASSERT(sstfq->right.qlen == 0);
404 DO_BEST_DEQ(sstfq->last_sector, req, &sstfq->left);
405 } else {
406 if (SNUM_DIFF(sstfq->last_sector, sstfq->right.queue->sectorOffset)
407 < SNUM_DIFF(sstfq->last_sector, sstfq->left.qtail->sectorOffset)) {
408 DO_HEAD_DEQ(req, &sstfq->right);
409 } else {
410 DO_TAIL_DEQ(req, &sstfq->left);
411 }
412 }
413 }
414 RF_ASSERT(req);
415 sstfq->last_sector = req->sectorOffset;
416 return (req);
417 }
418
419 RF_DiskQueueData_t *
420 rf_ScanDequeue(qptr)
421 void *qptr;
422 {
423 RF_DiskQueueData_t *req = NULL;
424 RF_Sstf_t *scanq;
425
426 scanq = (RF_Sstf_t *) qptr;
427
428 #if RF_DEBUG_QUEUE
429 if (rf_scanDebug) {
430 RF_DiskQueue_t *dq;
431 dq = (RF_DiskQueue_t *) req->queue;
432 RF_ASSERT(QSUM(scanq) == dq->queueLength);
433 printf("raid%d: scan: Dequeue %d queues are %d,%d,%d\n",
434 req->raidPtr->raidid, dq->col,
435 scanq->left.qlen, scanq->right.qlen, scanq->lopri.qlen);
436 }
437 #endif
438 if (scanq->left.queue == NULL) {
439 RF_ASSERT(scanq->left.qlen == 0);
440 if (scanq->right.queue == NULL) {
441 RF_ASSERT(scanq->right.qlen == 0);
442 if (scanq->lopri.queue == NULL) {
443 RF_ASSERT(scanq->lopri.qlen == 0);
444 return (NULL);
445 }
446 req = closest_to_arm(&scanq->lopri, scanq->last_sector,
447 &scanq->dir, scanq->allow_reverse);
448 if (req == NULL)
449 return (NULL);
450 do_dequeue(&scanq->lopri, req);
451 } else {
452 scanq->dir = DIR_RIGHT;
453 DO_HEAD_DEQ(req, &scanq->right);
454 }
455 } else
456 if (scanq->right.queue == NULL) {
457 RF_ASSERT(scanq->right.qlen == 0);
458 RF_ASSERT(scanq->left.queue);
459 scanq->dir = DIR_LEFT;
460 DO_TAIL_DEQ(req, &scanq->left);
461 } else {
462 RF_ASSERT(scanq->right.queue);
463 RF_ASSERT(scanq->left.queue);
464 if (scanq->dir == DIR_RIGHT) {
465 DO_HEAD_DEQ(req, &scanq->right);
466 } else {
467 DO_TAIL_DEQ(req, &scanq->left);
468 }
469 }
470 RF_ASSERT(req);
471 scanq->last_sector = req->sectorOffset;
472 return (req);
473 }
474
475 RF_DiskQueueData_t *
476 rf_CscanDequeue(qptr)
477 void *qptr;
478 {
479 RF_DiskQueueData_t *req = NULL;
480 RF_Sstf_t *cscanq;
481
482 cscanq = (RF_Sstf_t *) qptr;
483
484 RF_ASSERT(cscanq->dir == DIR_RIGHT);
485 #if RF_DEBUG_QUEUE
486 if (rf_cscanDebug) {
487 RF_DiskQueue_t *dq;
488 dq = (RF_DiskQueue_t *) req->queue;
489 RF_ASSERT(QSUM(cscanq) == dq->queueLength);
490 printf("raid%d: scan: Dequeue %d queues are %d,%d,%d\n",
491 req->raidPtr->raidid, dq->col,
492 cscanq->left.qlen, cscanq->right.qlen,
493 cscanq->lopri.qlen);
494 }
495 #endif
496 if (cscanq->right.queue) {
497 DO_HEAD_DEQ(req, &cscanq->right);
498 } else {
499 RF_ASSERT(cscanq->right.qlen == 0);
500 if (cscanq->left.queue == NULL) {
501 RF_ASSERT(cscanq->left.qlen == 0);
502 if (cscanq->lopri.queue == NULL) {
503 RF_ASSERT(cscanq->lopri.qlen == 0);
504 return (NULL);
505 }
506 req = closest_to_arm(&cscanq->lopri, cscanq->last_sector,
507 &cscanq->dir, cscanq->allow_reverse);
508 if (req == NULL)
509 return (NULL);
510 do_dequeue(&cscanq->lopri, req);
511 } else {
512 /*
513 * There's I/Os to the left of the arm. Swing
514 * on back (swap queues).
515 */
516 cscanq->right = cscanq->left;
517 cscanq->left.qlen = 0;
518 cscanq->left.queue = cscanq->left.qtail = NULL;
519 DO_HEAD_DEQ(req, &cscanq->right);
520 }
521 }
522 RF_ASSERT(req);
523 cscanq->last_sector = req->sectorOffset;
524 return (req);
525 }
526
527 RF_DiskQueueData_t *
528 rf_SstfPeek(qptr)
529 void *qptr;
530 {
531 RF_DiskQueueData_t *req;
532 RF_Sstf_t *sstfq;
533
534 sstfq = (RF_Sstf_t *) qptr;
535
536 if ((sstfq->left.queue == NULL) && (sstfq->right.queue == NULL)) {
537 req = closest_to_arm(&sstfq->lopri, sstfq->last_sector, &sstfq->dir,
538 sstfq->allow_reverse);
539 } else {
540 if (sstfq->left.queue == NULL)
541 req = sstfq->right.queue;
542 else {
543 if (sstfq->right.queue == NULL)
544 req = sstfq->left.queue;
545 else {
546 if (SNUM_DIFF(sstfq->last_sector, sstfq->right.queue->sectorOffset)
547 < SNUM_DIFF(sstfq->last_sector, sstfq->left.qtail->sectorOffset)) {
548 req = sstfq->right.queue;
549 } else {
550 req = sstfq->left.qtail;
551 }
552 }
553 }
554 }
555 if (req == NULL) {
556 RF_ASSERT(QSUM(sstfq) == 0);
557 }
558 return (req);
559 }
560
561 RF_DiskQueueData_t *
562 rf_ScanPeek(qptr)
563 void *qptr;
564 {
565 RF_DiskQueueData_t *req;
566 RF_Sstf_t *scanq;
567 int dir;
568
569 scanq = (RF_Sstf_t *) qptr;
570 dir = scanq->dir;
571
572 if (scanq->left.queue == NULL) {
573 RF_ASSERT(scanq->left.qlen == 0);
574 if (scanq->right.queue == NULL) {
575 RF_ASSERT(scanq->right.qlen == 0);
576 if (scanq->lopri.queue == NULL) {
577 RF_ASSERT(scanq->lopri.qlen == 0);
578 return (NULL);
579 }
580 req = closest_to_arm(&scanq->lopri, scanq->last_sector,
581 &dir, scanq->allow_reverse);
582 } else {
583 req = scanq->right.queue;
584 }
585 } else
586 if (scanq->right.queue == NULL) {
587 RF_ASSERT(scanq->right.qlen == 0);
588 RF_ASSERT(scanq->left.queue);
589 req = scanq->left.qtail;
590 } else {
591 RF_ASSERT(scanq->right.queue);
592 RF_ASSERT(scanq->left.queue);
593 if (scanq->dir == DIR_RIGHT) {
594 req = scanq->right.queue;
595 } else {
596 req = scanq->left.qtail;
597 }
598 }
599 if (req == NULL) {
600 RF_ASSERT(QSUM(scanq) == 0);
601 }
602 return (req);
603 }
604
605 RF_DiskQueueData_t *
606 rf_CscanPeek(qptr)
607 void *qptr;
608 {
609 RF_DiskQueueData_t *req;
610 RF_Sstf_t *cscanq;
611
612 cscanq = (RF_Sstf_t *) qptr;
613
614 RF_ASSERT(cscanq->dir == DIR_RIGHT);
615 if (cscanq->right.queue) {
616 req = cscanq->right.queue;
617 } else {
618 RF_ASSERT(cscanq->right.qlen == 0);
619 if (cscanq->left.queue == NULL) {
620 RF_ASSERT(cscanq->left.qlen == 0);
621 if (cscanq->lopri.queue == NULL) {
622 RF_ASSERT(cscanq->lopri.qlen == 0);
623 return (NULL);
624 }
625 req = closest_to_arm(&cscanq->lopri, cscanq->last_sector,
626 &cscanq->dir, cscanq->allow_reverse);
627 } else {
628 /*
629 * There's I/Os to the left of the arm. We'll end
630 * up swinging on back.
631 */
632 req = cscanq->left.queue;
633 }
634 }
635 if (req == NULL) {
636 RF_ASSERT(QSUM(cscanq) == 0);
637 }
638 return (req);
639 }
640
641 int
642 rf_SstfPromote(qptr, parityStripeID, which_ru)
643 void *qptr;
644 RF_StripeNum_t parityStripeID;
645 RF_ReconUnitNum_t which_ru;
646 {
647 RF_DiskQueueData_t *r, *next;
648 RF_Sstf_t *sstfq;
649 int n;
650
651 sstfq = (RF_Sstf_t *) qptr;
652
653 n = 0;
654 for (r = sstfq->lopri.queue; r; r = next) {
655 next = r->next;
656 #if RF_DEBUG_QUEUE
657 if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
658 printf("raid%d: check promote %lx\n",
659 r->raidPtr->raidid, (long) r);
660 }
661 #endif
662 if ((r->parityStripeID == parityStripeID)
663 && (r->which_ru == which_ru)) {
664 do_dequeue(&sstfq->lopri, r);
665 rf_SstfEnqueue(qptr, r, RF_IO_NORMAL_PRIORITY);
666 n++;
667 }
668 }
669 #if RF_DEBUG_QUEUE
670 if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
671 printf("raid%d: promoted %d matching I/Os queues are %d,%d,%d\n",
672 r->raidPtr->raidid, n, sstfq->left.qlen,
673 sstfq->right.qlen, sstfq->lopri.qlen);
674 }
675 #endif
676 return (n);
677 }
Cache object: b8ee313d149256ced7602195bcff3eab
|