1 /*
  2  * Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package nsk.share.locks;
 24 
 25 import java.util.*;
 26 import java.util.concurrent.locks.ReentrantLock;
 27 
 28 import nsk.share.Consts;
 29 import nsk.share.Log;
 30 import nsk.share.TestBug;
 31 import nsk.share.TestJNIError;
 32 import nsk.share.Wicket;
 33 
 34 /*
 35  Thread with possibility acquiring monitors in different ways:
 36  - entering synchronized method
 37  - entering synchronized method for thread object itself
 38  - entering synchronized static method
 39  - entering synchronized method for thread class itself
 40  - entering synchronized block on non-static object
 41  - entering synchronized block on non-static on thread object itself
 42  - entering synchronized block on static object
 43  - entering synchronized block on static thread object itself
 44  - JNI MonitorEnter.
 45 
 46  Description of required thread stack should be passed to LockingThread in constructor.
 47  When started locking thread create required stack and sleep until not interrupted.
 48 
 49  LockingThread can relinquish acquired monitors in follows ways:
 50  - relinquish single monitor through Object.wait - relinquishMonitor(int monitorIndex),
 51  - relinquish single monitor through exiting from synchronized blocks/methods or through JNI MonitorExit  - exitSingleFrame(),
 52  - relinquish all monitors(exit from all synchronized blocks/methods) - stopLockingThread()
 53 
 54  Debug information about each acquired/relinquished monitor is stored and can be obtained through getMonitorsInfo().
 55 
 56  To be sure that LockingThread have reached required state call method LockingThread.waitState().
 57 
 58  Usage example:
 59 
 60  List<String> stackFramesDescription = new ArrayList<String>();
 61  stackFramesDescription.add(LockingThread.SYNCHRONIZED_METHOD);
 62  stackFramesDescription.add(LockingThread.SYNCHRONIZED_OBJECT_BLOCK);
 63 
 64  LockingThread lockingThread = new LockingThread(log, stackFramesDescription);
 65 
 66  lockingThread.start();
 67 
 68  // after calling waitState() LockingThread should complete stack creation
 69   lockingThread.waitState();
 70 
 71   lockingThread.exitSingleFrame();
 72 
 73   // after calling waitState() LockingThread should complete exit from stack frame
 74    lockingThread.waitState();
 75    */
 76 public class LockingThread extends Thread {
 77     // native part uses TestJNIError class
 78     private static final Class<?> jniErrorKlass = TestJNIError.class;
 79     static {
 80         try {
 81             System.loadLibrary("LockingThread");
 82         } catch (UnsatisfiedLinkError e) {
 83             System.out.println("Unexpected UnsatisfiedLinkError on loading library 'LockingThread'");
 84             e.printStackTrace(System.out);
 85             System.exit(Consts.JCK_STATUS_BASE + Consts.TEST_FAILED);
 86         }
 87     }
 88 
 89     /*
 90      *  Information about acquired monitor
 91      */
 92     public static class DebugMonitorInfo {
 93         public DebugMonitorInfo(Object monitor, int stackDepth, Thread thread, boolean isNative) {
 94             this.monitor = monitor;
 95             this.stackDepth = stackDepth;
 96             this.thread = thread;
 97             this.isNative = isNative;
 98         }
 99 
100         public Object monitor;
101 
102         public int stackDepth;
103 
104         public Thread thread;
105 
106         boolean isNative;
107     }
108 
109     // acquire JNI monitor through JNIMonitorEnter()
110     public static final String JNI_MONITOR_ENTER = "JNI_MONITOR_ENTER";
111 
112     // entering synchronized static method
113     public static final String SYNCHRONIZED_STATIC_METHOD = "SYNCHRONIZED_STATIC_METHOD";
114 
115     // entering synchronized static method for thread class itself
116     public static final String SYNCHRONIZED_STATIC_THREAD_METHOD = "SYNCHRONIZED_STATIC_THREAD_METHOD";
117 
118     // entering synchronized method
119     public static final String SYNCHRONIZED_METHOD = "SYNCHRONIZED_METHOD";
120 
121     // entering synchronized method for thread object itself
122     public static final String SYNCHRONIZED_THREAD_METHOD = "SYNCHRONIZED_THREAD_METHOD";
123 
124     // entering synchronized block for thread object itself
125     public static final String SYNCHRONIZED_THIS_BLOCK = "SYNCHRONIZED_THIS_BLOCK";
126 
127     // entering synchronized block
128     public static final String SYNCHRONIZED_OBJECT_BLOCK = "SYNCHRONIZED_OBJECT_BLOCK";
129 
130     // entering synchronized block on static object
131     public static final String SYNCHRONIZED_BLOCK_STATIC_OBJECT = "SYNCHRONIZED_BLOCK_STATIC_OBJECT";
132 
133     // entering synchronized block on static thread object itself
134     public static final String SYNCHRONIZED_BLOCK_STATIC_THREAD_OBJECT = "SYNCHRONIZED_BLOCK_STATIC_THREAD_OBJECT";
135 
136     // entering frame without monitor acquiring
137     public static final String FRAME_WITHOUT_LOCK = "FRAME_WITHOUT_LOCK";
138 
139     // all acquired monitors
140     private List<DebugMonitorInfo> monitorsInfo = new ArrayList<DebugMonitorInfo>();
141 
142     // This parameter should be passed in constructor
143     // It describe how many locks and in which way LockingThread should acquire
144     private List<String> stackFramesDescription;
145 
146     private Log log;
147 
148     // is during LockingThread's operations any errors occurred
149     private boolean executedWithErrors;
150 
151     public boolean isExecutedWithErrors() {
152         return executedWithErrors;
153     }
154 
155     public LockingThread(Log log, List<String> stackFramesDescription) {
156         this.log = log;
157         this.stackFramesDescription = stackFramesDescription;
158     }
159 
160     // return array containing all acquired monitors
161     public DebugMonitorInfo[] getMonitorsInfo(boolean returnJNIMonitors) {
162         Map<Object, DebugMonitorInfo> result = new HashMap<Object, DebugMonitorInfo>();
163 
164         for (int i = monitorsInfo.size() - 1; i >= 0; i--) {
165             DebugMonitorInfo monitorInfo = monitorsInfo.get(i);
166 
167             if ((returnJNIMonitors || !monitorInfo.isNative) &&
168 
169             // don't return relinquished monitors
170                     (monitorInfo.monitor != relinquishedMonitor) &&
171 
172                     // return only last monitor occurrence
173                     !result.containsKey(monitorInfo.monitor)) {
174                 result.put(monitorInfo.monitor, monitorInfo);
175             }
176         }
177 
178         return result.values().toArray(new DebugMonitorInfo[] {});
179     }
180 
181     void log(String message) {
182         log.display(Thread.currentThread().getName() + ": " + message);
183     }
184 
185     // add debug information about acquired monitor
186     void addMonitorInfo(DebugMonitorInfo monitorInfo) {
187         monitorsInfo.add(monitorInfo);
188     }
189 
190     // remove debug information about acquired monitor (also should update information about stack depth)
191     void removeMonitorInfo(DebugMonitorInfo removedMonitor) {
192         for (DebugMonitorInfo monitor : monitorsInfo) {
193             if (monitor.stackDepth > removedMonitor.stackDepth)
194                 monitor.stackDepth -= 2;
195         }
196 
197         monitorsInfo.remove(removedMonitor);
198     }
199 
200     // used for stack frames creation
201     private int currentIndex;
202 
203     // Recursive function used for stack frames creation
204 
205     // For example if LockingThread should acquire 1 monitor through synchronized block
206     // and 1 monitor through synchronized method pass list with values SYNCHRONIZED_METHOD and SYNCHRONIZED_OBJECT_BLOCK
207     // to the constructor and after running LockingThread will have following stack frames:
208 
209     // run()
210     //  createStackFrame()
211     //      ClassWithSynchronizedMethods().synchronizedMethod() // monitor for instance of ClassWithSynchronizedMethods is acquired here
212     //          createStackFrame()
213     //              synchronizedObjectBlock()   // monitor for instance of Object is acquired here
214     //                  createStackFrame()
215     //                      doWait()
216     //                          sleep()
217 
218     // When LockingThread have created required stack frame it calls method doWait() and sleep(Long.MAX_VALUE)
219 
220     // If LockingThread should relinquish one of the acquired monitors it should be interrupted and after
221     // interrupting should call 'wait()' for specified monitor, and for this example LockingThread will have
222     // following stack frames:
223 
224     // run()
225     //  createStackFrame()
226     //      ClassWithSynchronizedMethods().synchronizedMethod() // monitor for instance of ClassWithSynchronizedMethods is acquired here
227     //          createStackFrame()
228     //              synchronizedObjectBlock()   // monitor for instance of Object is acquired here
229     //                  createStackFrame()
230     //                      doWait()
231     //                          relinquishedMonitor.wait()
232 
233     // LockingThread still holds all other locks because of it didn't exit from corresponding synchronized methods and blocks.
234     // To let LockingThread acquire relinquished monitor 'relinquishedMonitor.notifyAll()' should be called, after this
235     // LockingThread will acquire this monitor again because of it still in corresponding synchronized method or block and
236     // it will have again such stack frames:
237 
238     // run()
239     //  createStackFrame()
240     //      ClassWithSynchronizedMethods().synchronizedMethod() // monitor for instance of ClassWithSynchronizedMethods is acquired here
241     //          createStackFrame()
242     //              synchronizedObjectBlock()   // monitor for instance of Object is acquired here
243     //                  createStackFrame()
244     //                      doWait()
245     //                          sleep()
246     void createStackFrame() {
247         if (currentIndex < stackFramesDescription.size()) {
248             String frameDescription = stackFramesDescription.get(currentIndex);
249 
250             currentIndex++;
251 
252             if (frameDescription.equals(JNI_MONITOR_ENTER)) {
253                 // for JNI monitors -1 is returned as stack depth
254                 int currentStackDepth = -1;
255                 Object object = new Object();
256                 DebugMonitorInfo monitorInfo = new DebugMonitorInfo(object, currentStackDepth, this, true);
257                 addMonitorInfo(monitorInfo);
258                 log("Enter JNI monitor");
259                 nativeJNIMonitorEnter(object);
260                 log("Exit JNI monitor");
261                 removeMonitorInfo(monitorInfo);
262             } else if (frameDescription.equals(SYNCHRONIZED_BLOCK_STATIC_OBJECT)) {
263                 synchronizedBlockStaticObject();
264             } else if (frameDescription.equals(SYNCHRONIZED_BLOCK_STATIC_THREAD_OBJECT)) {
265                 synchronizedBlockStaticThreadObject();
266             } else if (frameDescription.equals(SYNCHRONIZED_METHOD)) {
267                 new ClassWithSynchronizedMethods().synchronizedMethod(this);
268             } else if (frameDescription.equals(SYNCHRONIZED_THREAD_METHOD)) {
269                 synchronizedMethod();
270             } else if (frameDescription.equals(SYNCHRONIZED_STATIC_METHOD)) {
271                 ClassWithSynchronizedMethods.synchronizedStaticMethod(this);
272             } else if (frameDescription.equals(SYNCHRONIZED_STATIC_THREAD_METHOD)) {
273                 synchronizedStaticMethod(this);
274             } else if (frameDescription.equals(SYNCHRONIZED_THIS_BLOCK)) {
275                 synchronizedThisBlock();
276             } else if (frameDescription.equals(SYNCHRONIZED_OBJECT_BLOCK)) {
277                 synchronizedObjectBlock();
278             } else if (frameDescription.equals(FRAME_WITHOUT_LOCK)) {
279                 frameWithoutLock();
280             } else
281                 throw new TestBug("Invalid stack frame description: " + frameDescription);
282         } else {
283             // required stack is created
284             ready();
285             doWait();
286         }
287 
288         if (exitSingleFrame) {
289             if (currentIndex-- < stackFramesDescription.size()) {
290                 // exit from single synchronized block/method
291                 ready();
292                 doWait();
293             }
294         }
295     }
296 
297     public Object getRelinquishedMonitor() {
298         return relinquishedMonitor;
299     }
300 
301     private Object relinquishedMonitor;
302 
303     private Wicket waitStateWicket = new Wicket();
304 
305     private Thread.State requiredState;
306 
307     public void waitState() {
308         // try wait with timeout to avoid possible hanging (if LockingThread have finished execution because of uncaught exception)
309         int result = waitStateWicket.waitFor(60000);
310 
311         if (result != 0) {
312             throw new TestBug("Locking thread can't reach required state (waitStateWicket wasn't unlocked)");
313         }
314 
315         if (requiredState == null)
316             throw new TestBug("Required state not specified");
317 
318         long startTime = System.currentTimeMillis();
319 
320         // additional check to be sure that LockingThread acquired state
321         while (this.getState() != requiredState) {
322 
323             // try wait with timeout to avoid possible hanging if something will go wrong
324             if ((System.currentTimeMillis() - startTime) > 60000) {
325                 throw new TestBug("Locking thread can't reach required state (state: " + requiredState + " wasn't reached) in 1 minute");
326             }
327 
328             Thread.yield();
329         }
330 
331         requiredState = null;
332 
333         Object relinquishedMonitor = getRelinquishedMonitor();
334         /*
335          * Changing thread state and release of lock is not single/atomic operation.
336          * As result there is a potential race when thread state (LockingThread) has
337          * been changed but the lock has not been released yet. To avoid this current
338          * thread is trying to acquire the same monitor, so current thread proceeds
339          * execution only when monitor has been really relinquished by LockingThread.
340          */
341         if (relinquishedMonitor != null) {
342             synchronized (relinquishedMonitor) {
343             }
344         }
345     }
346 
347     // LockingThread acquired required state
348     private void ready() {
349         waitStateWicket.unlockAll();
350     }
351 
352     // is LockingThread should relinquish single monitor
353     private volatile boolean relinquishMonitor;
354 
355     private int relinquishedMonitorIndex;
356 
357     // relinquish single monitor with given index through Object.wait()
358     public void relinquishMonitor(int index) {
359         if (index >= monitorsInfo.size()) {
360             throw new TestBug("Invalid monitor index: " + index);
361         }
362 
363         requiredState = Thread.State.WAITING;
364         waitStateWicket = new Wicket();
365         relinquishMonitor = true;
366         relinquishedMonitorIndex = index;
367 
368         interrupt();
369 
370         DebugMonitorInfo monitorInfo = monitorsInfo.get(relinquishedMonitorIndex);
371 
372         if (monitorInfo == null)
373             throw new TestBug("Invalid monitor index: " + relinquishedMonitorIndex);
374     }
375 
376     public void acquireRelinquishedMonitor() {
377         if (relinquishedMonitor == null) {
378             throw new TestBug("There is no relinquished monitor");
379         }
380 
381         // Set requiredState to 'Thread.State.TIMED_WAITING' because of LockingThread call
382         // Thread.sleep(Long.MAX_VALUE) after monitor acquiring
383         requiredState = Thread.State.TIMED_WAITING;
384 
385         waitStateWicket = new Wicket();
386         relinquishMonitor = false;
387 
388         synchronized (relinquishedMonitor) {
389             relinquishedMonitor.notifyAll();
390         }
391     }
392 
393     public void stopLockingThread() {
394         requiredState = Thread.State.TIMED_WAITING;
395 
396         waitStateWicket = new Wicket();
397         exitSingleFrame = false;
398 
399         interrupt();
400     }
401 
402     // is thread should exit from single synchronized block/method
403     private boolean exitSingleFrame;
404 
405     public void exitSingleFrame() {
406         requiredState = Thread.State.TIMED_WAITING;
407 
408         waitStateWicket = new Wicket();
409         exitSingleFrame = true;
410 
411         interrupt();
412     }
413 
414     // LockingThread call this method when required state is reached
415     private void doWait() {
416         while (true) {
417             try {
418                 Thread.sleep(Long.MAX_VALUE);
419             } catch (InterruptedException e) {
420                 // expected exception, LockingThread should be interrupted to change state
421             }
422 
423             // if single monitor should be relinquished through Object.wait()
424             if (relinquishMonitor) {
425                 try {
426                     DebugMonitorInfo monitorInfo = monitorsInfo.get(relinquishedMonitorIndex);
427 
428                     if (monitorInfo == null)
429                         throw new TestBug("Invalid monitor index: " + relinquishedMonitorIndex);
430 
431                     relinquishedMonitor = monitorInfo.monitor;
432 
433                     log("Relinquish monitor: " + relinquishedMonitor);
434 
435                     // monitor is relinquished
436                     ready();
437 
438                     // Really monitor is relinquished only when LockingThread calls relinquishedMonitor.wait(0) below,
439                     // but to be sure that LockingThread have reached required state method waitState() should be called
440                     // and this method waits when LockingThred change state to 'Thread.State.WAITING'
441 
442                     while (relinquishMonitor)
443                         relinquishedMonitor.wait(0);
444 
445                     log("Acquire relinquished monitor: " + relinquishedMonitor);
446                 } catch (Exception e) {
447                     executedWithErrors = true;
448                     log("Unexpected exception: " + e);
449                     e.printStackTrace(log.getOutStream());
450                 }
451 
452                 relinquishedMonitor = null;
453 
454                 // monitor is acquired again
455                 //(becuase we still are located in the frame where lock was acquired before we relinquished it)
456                 ready();
457             } else
458                 // exit from frame
459                 break;
460         }
461     }
462 
463     public void run() {
464         // LockingThread call Thread.sleep() when required stack frame was created
465         requiredState = Thread.State.TIMED_WAITING;
466 
467         createStackFrame();
468 
469         // thread relinquished all monitors
470         ready();
471         doWait();
472     }
473 
474     static synchronized void synchronizedStaticMethod(LockingThread lockingThread) {
475         int currentStackDepth = lockingThread.expectedDepth();
476 
477         lockingThread.log("Enter synchronized static thread method");
478 
479         DebugMonitorInfo monitorInfo = new DebugMonitorInfo(LockingThread.class, currentStackDepth, lockingThread, false);
480         lockingThread.addMonitorInfo(monitorInfo);
481         lockingThread.createStackFrame();
482         lockingThread.removeMonitorInfo(monitorInfo);
483 
484         lockingThread.log("Exit synchronized static thread method");
485     }
486 
487     // calculate stack depth at which monitor was acquired
488     int expectedDepth() {
489         // for each monitor call 2 methods: createStackFrame() and method which acquire monitor
490         // + when stack creation is finished call 2 methods: createStackFrame()->doWait()->sleep()/wait()
491         // implementation of Object.wait() and Thread.sleep() are excluded from comparison
492         return (stackFramesDescription.size() - currentIndex) * 2 + 3;
493     }
494 
495     private native void nativeJNIMonitorEnter(Object object);
496 
497     synchronized void synchronizedMethod() {
498         int currentStackDepth = expectedDepth();
499 
500         log("Enter synchronized thread method");
501 
502         DebugMonitorInfo monitorInfo = new DebugMonitorInfo(this, currentStackDepth, this, false);
503         addMonitorInfo(monitorInfo);
504         createStackFrame();
505         removeMonitorInfo(monitorInfo);
506 
507         log("Exit synchronized thread method");
508     }
509 
510     void synchronizedThisBlock() {
511         int currentStackDepth = expectedDepth();
512 
513         log("Enter synchronized(this) block");
514 
515         synchronized (this) {
516             DebugMonitorInfo monitorInfo = new DebugMonitorInfo(this, currentStackDepth, this, false);
517             addMonitorInfo(monitorInfo);
518             createStackFrame();
519             removeMonitorInfo(monitorInfo);
520         }
521 
522         log("Exit synchronized(this) block");
523     }
524 
525     private static Object staticObject;
526 
527     // 'staticObjectInitializingLock' is used in synchronizedBlockStaticObject() and synchronizedBlockStaticThreadObject().
528     // In this methods LockingThread initializes static object and enters in synchronized block
529     // for this static object, this actions are not thread safe(because of static fields are used) and 'staticObjectInitializingLock'
530     // is used to prevent races
531     private static ReentrantLock staticObjectInitializingLock = new ReentrantLock();
532 
533     void synchronizedBlockStaticObject() {
534         int currentStackDepth = expectedDepth();
535 
536         // initializing of 'staticObject' and entering to the synchronized(staticObject) block should be thread safe
537         staticObjectInitializingLock.lock();
538 
539         staticObject = new Object();
540 
541         log("Enter synchronized(static object) block");
542 
543         synchronized (staticObject) {
544             // thread unsafe actions was done
545             staticObjectInitializingLock.unlock();
546 
547             DebugMonitorInfo monitorInfo = new DebugMonitorInfo(staticObject, currentStackDepth, this, false);
548             addMonitorInfo(monitorInfo);
549             createStackFrame();
550             removeMonitorInfo(monitorInfo);
551         }
552 
553         log("Exit synchronized(static object) block");
554     }
555 
556     private static LockingThread staticLockingThread;
557 
558     void synchronizedBlockStaticThreadObject() {
559         int currentStackDepth = expectedDepth();
560 
561         // initializing of 'staticLockingThread' and entering to the synchronized(staticLockingThread) block should be thread safe
562         staticObjectInitializingLock.lock();
563 
564         staticLockingThread = this;
565 
566         log("Enter synchronized(static thread object) block");
567 
568         synchronized (staticLockingThread) {
569             // thread unsafe actions was done
570             staticObjectInitializingLock.unlock();
571 
572             DebugMonitorInfo monitorInfo = new DebugMonitorInfo(staticLockingThread, currentStackDepth, this, false);
573             addMonitorInfo(monitorInfo);
574             createStackFrame();
575             removeMonitorInfo(monitorInfo);
576         }
577 
578         log("Exit synchronized(static thread object) block");
579     }
580 
581     void synchronizedObjectBlock() {
582         int currentStackDepth = expectedDepth();
583 
584         Object object = new Object();
585 
586         log("Enter synchronized(object) block");
587 
588         synchronized (object) {
589             DebugMonitorInfo monitorInfo = new DebugMonitorInfo(object, currentStackDepth, this, false);
590             addMonitorInfo(monitorInfo);
591             createStackFrame();
592             removeMonitorInfo(monitorInfo);
593         }
594 
595         log("Exit synchronized(object) block");
596     }
597 
598     private void frameWithoutLock() {
599         log("Enter frameWithoutLock");
600 
601         createStackFrame();
602 
603         for (DebugMonitorInfo monitor : monitorsInfo)
604             monitor.stackDepth -= 2;
605 
606         log("Exit frameWithoutLock");
607     }
608 }
609 
610 //class containing synchronized method and synchronized static method, used by LockingThread
611 class ClassWithSynchronizedMethods {
612     public synchronized void synchronizedMethod(LockingThread lockingThread) {
613         int currentStackDepth = lockingThread.expectedDepth();
614 
615         lockingThread.log("Enter synchronized method");
616 
617         LockingThread.DebugMonitorInfo monitorInfo = new LockingThread.DebugMonitorInfo(this, currentStackDepth, lockingThread, false);
618         lockingThread.addMonitorInfo(monitorInfo);
619         lockingThread.createStackFrame();
620         lockingThread.removeMonitorInfo(monitorInfo);
621 
622         lockingThread.log("Exit synchronized method");
623     }
624 
625     public static synchronized void synchronizedStaticMethod(LockingThread lockingThread) {
626         int currentStackDepth = lockingThread.expectedDepth();
627 
628         lockingThread.log("Enter synchronized static method");
629 
630         LockingThread.DebugMonitorInfo monitorInfo = new LockingThread.DebugMonitorInfo(ClassWithSynchronizedMethods.class, currentStackDepth,
631                 lockingThread, false);
632         lockingThread.addMonitorInfo(monitorInfo);
633         lockingThread.createStackFrame();
634         lockingThread.removeMonitorInfo(monitorInfo);
635 
636         lockingThread.log("Exit synchronized static method");
637     }
638 }