112f608f3d2089439a108788a1908941eea4277b9Puneet Lall/*
263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall * Copyright (C) 2015 The Android Open Source Project
312f608f3d2089439a108788a1908941eea4277b9Puneet Lall *
412f608f3d2089439a108788a1908941eea4277b9Puneet Lall * Licensed under the Apache License, Version 2.0 (the "License");
512f608f3d2089439a108788a1908941eea4277b9Puneet Lall * you may not use this file except in compliance with the License.
612f608f3d2089439a108788a1908941eea4277b9Puneet Lall * You may obtain a copy of the License at
712f608f3d2089439a108788a1908941eea4277b9Puneet Lall *
812f608f3d2089439a108788a1908941eea4277b9Puneet Lall *      http://www.apache.org/licenses/LICENSE-2.0
912f608f3d2089439a108788a1908941eea4277b9Puneet Lall *
1012f608f3d2089439a108788a1908941eea4277b9Puneet Lall * Unless required by applicable law or agreed to in writing, software
1112f608f3d2089439a108788a1908941eea4277b9Puneet Lall * distributed under the License is distributed on an "AS IS" BASIS,
1212f608f3d2089439a108788a1908941eea4277b9Puneet Lall * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1312f608f3d2089439a108788a1908941eea4277b9Puneet Lall * See the License for the specific language governing permissions and
1412f608f3d2089439a108788a1908941eea4277b9Puneet Lall * limitations under the License.
1512f608f3d2089439a108788a1908941eea4277b9Puneet Lall */
1612f608f3d2089439a108788a1908941eea4277b9Puneet Lall
1712f608f3d2089439a108788a1908941eea4277b9Puneet Lallpackage com.android.camera.one.v2.sharedimagereader.ringbuffer;
1812f608f3d2089439a108788a1908941eea4277b9Puneet Lall
1912f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.async.BufferQueue;
2012f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.async.BufferQueueController;
2163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.ConcurrentState;
2263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.CountableBufferQueue;
2363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.Observable;
2412f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.one.v2.camera2proxy.ImageProxy;
2512f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.one.v2.sharedimagereader.ticketpool.Ticket;
2663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.ticketpool.TicketPool;
2763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.util.ImageCloser;
2863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.util.TicketImageProxy;
2936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lallimport com.google.common.base.Preconditions;
3063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
3163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.Arrays;
3263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.Collection;
3363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.TimeUnit;
3463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.TimeoutException;
3563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.atomic.AtomicInteger;
3612f608f3d2089439a108788a1908941eea4277b9Puneet Lall
374961ad31d9a877e3a68566fb5d4b33b7f79ce44ePuneet Lallimport javax.annotation.Nonnull;
3863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport javax.annotation.Nullable;
3963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport javax.annotation.ParametersAreNonnullByDefault;
4036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lallimport javax.annotation.concurrent.ThreadSafe;
4136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall
4236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall/**
4336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * A dynamically-sized ring-buffer, implementing BufferQueue (output) and
4436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * BufferQueueController (input).
4536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p>
4636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * The size of the buffer is implicitly defined by the number of "Tickets"
4736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * available from the parent {@link TicketPool} at any given time. When the
4836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * number of available tickets decreases, the buffer shrinks, discarding old
4936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * elements. When the number of available tickets increases, the buffer expands,
5036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * retaining old elements when new elements are added.
5136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p>
5236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * The ring-buffer is also a TicketPool, which allows higher-priority requests
5336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * to reserve "Tickets" (representing ImageReader capacity) to evict images from
5436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * the ring-buffer.
5536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p>
5636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * See docs for {@link DynamicRingBufferFactory} for more information.
5736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall */
5836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall@ThreadSafe
5963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall@ParametersAreNonnullByDefault
6063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallfinal class DynamicRingBuffer implements TicketPool, BufferQueue<ImageProxy>,
6163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        BufferQueueController<ImageProxy> {
6263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    private final CountableBufferQueue<ImageProxy> mQueue;
6363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    private final TicketPool mTicketPool;
6463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    private final AtomicInteger mTicketWaiterCount;
6563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    private final AvailableTicketCounter mAvailableTicketCount;
6636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    private final AtomicInteger mMaxSize;
6736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    private final ConcurrentState<Integer> mQueueSize;
6863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
6936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    /**
7036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall     * @param parentTickets The parent ticket pool which implicitly determines
7136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall     *            how much capacity is available at any given time.
7236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall     */
7363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    DynamicRingBuffer(TicketPool parentTickets) {
7436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mQueueSize = new ConcurrentState<>(0);
7536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mQueue = new CountableBufferQueue<>(mQueueSize, new ImageCloser());
7636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mAvailableTicketCount = new AvailableTicketCounter(Arrays.asList(mQueueSize, parentTickets
7763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                .getAvailableTicketCount()));
7863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mTicketPool = parentTickets;
7963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mTicketWaiterCount = new AtomicInteger(0);
8036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mMaxSize = new AtomicInteger(Integer.MAX_VALUE);
8112f608f3d2089439a108788a1908941eea4277b9Puneet Lall    }
8212f608f3d2089439a108788a1908941eea4277b9Puneet Lall
8312f608f3d2089439a108788a1908941eea4277b9Puneet Lall    @Override
844961ad31d9a877e3a68566fb5d4b33b7f79ce44ePuneet Lall    public void update(@Nonnull ImageProxy image) {
8512f608f3d2089439a108788a1908941eea4277b9Puneet Lall        // Try to acquire a ticket to expand the ring-buffer and save the image.
8663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        Ticket ticket = null;
8763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
8863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // Counting is hard. {@link mAvailableTicketCount} must reflect the sum
8963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // of mTicketPool.getAvailableTicketCount() and the number of images in
9063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // mQueue. However, for a brief moment, we acquire a ticket from
9163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // mTicketPool, but have yet added it to mQueue. During this period,
9263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // mAvailableTicketCount would appear to be 1 less than it should.
9363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // To fix this, we must lock it to the current value, perform the
9463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // transaction, and then unlock it, marking it as "valid" again, which
9563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        // also notifies listeners of the change.
9663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mAvailableTicketCount.freeze();
9763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        try {
9863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            ticket = tryAcquireLowPriorityTicket();
9963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            if (ticket == null) {
10063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                // If we cannot expand the ring-buffer, remove the last element
10163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                // (decreasing the size), and then try to increase the size
10263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                // again.
10363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                mQueue.discardNext();
10463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                ticket = tryAcquireLowPriorityTicket();
10563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            }
10663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            if (ticket != null) {
10763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                mQueue.update(new TicketImageProxy(image, ticket));
10863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            } else {
10963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                image.close();
11063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            }
11136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            shrinkToFitMaxSize();
11263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        } finally {
11363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            mAvailableTicketCount.unfreeze();
11412f608f3d2089439a108788a1908941eea4277b9Puneet Lall        }
11563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
11663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
11763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Nullable
11863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    private Ticket tryAcquireLowPriorityTicket() {
11963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        if (mTicketWaiterCount.get() != 0) {
12063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            return null;
12112f608f3d2089439a108788a1908941eea4277b9Puneet Lall        }
12263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mTicketPool.tryAcquire();
12312f608f3d2089439a108788a1908941eea4277b9Puneet Lall    }
12412f608f3d2089439a108788a1908941eea4277b9Puneet Lall
12512f608f3d2089439a108788a1908941eea4277b9Puneet Lall    @Override
12612f608f3d2089439a108788a1908941eea4277b9Puneet Lall    public void close() {
12763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mQueue.close();
12863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
12963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
13063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
13163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public ImageProxy getNext() throws InterruptedException, BufferQueueClosedException {
13263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mQueue.getNext();
13363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
13463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
13563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
13663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public ImageProxy getNext(long timeout, TimeUnit unit) throws InterruptedException,
13763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            TimeoutException, BufferQueueClosedException {
13863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mQueue.getNext(timeout, unit);
13963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
14063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
14163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
14263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public ImageProxy peekNext() {
14363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mQueue.peekNext();
14463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
14563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
14663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
14763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public void discardNext() {
14863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mQueue.discardNext();
14912f608f3d2089439a108788a1908941eea4277b9Puneet Lall    }
15012f608f3d2089439a108788a1908941eea4277b9Puneet Lall
15112f608f3d2089439a108788a1908941eea4277b9Puneet Lall    @Override
15212f608f3d2089439a108788a1908941eea4277b9Puneet Lall    public boolean isClosed() {
15363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mQueue.isClosed();
15463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
15563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
15663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Nonnull
15763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
15863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public Collection<Ticket> acquire(int tickets) throws InterruptedException,
15963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            NoCapacityAvailableException {
16063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mTicketWaiterCount.incrementAndGet();
16163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        try {
16263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            while (mQueue.peekNext() != null) {
16363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                mQueue.discardNext();
16463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            }
16563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            return mTicketPool.acquire(tickets);
16663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        } finally {
16763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            mTicketWaiterCount.decrementAndGet();
16863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        }
16963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
17063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
17163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Nonnull
17263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
17363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public Observable<Integer> getAvailableTicketCount() {
17463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        return mAvailableTicketCount;
17563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    }
17663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall
17763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Nullable
17863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    @Override
17963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall    public Ticket tryAcquire() {
18063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        mTicketWaiterCount.incrementAndGet();
18163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        try {
18263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            while (mQueue.peekNext() != null) {
18363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall                mQueue.discardNext();
18463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            }
18563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            return mTicketPool.tryAcquire();
18663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        } finally {
18763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall            mTicketWaiterCount.decrementAndGet();
18863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall        }
18912f608f3d2089439a108788a1908941eea4277b9Puneet Lall    }
19036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall
19136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    public void setMaxSize(int newMaxSize) {
19236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        Preconditions.checkArgument(newMaxSize >= 0);
19336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mMaxSize.set(newMaxSize);
19436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        // Shrink the queue to meet this new constraint.
19536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        shrinkToFitMaxSize();
19636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    }
19736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall
19836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    private void shrinkToFitMaxSize() {
19936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        // To ensure that the available ticket count never "flickers" when we
20036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        // logically move the ticket from the queue into the parent ticket pool,
20136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        // lock the available ticket count.
20236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        mAvailableTicketCount.freeze();
20336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        try {
20436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            // Note that to maintain the invariant of eventual-consistency
20536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            // (since this class is inherently shared between multiple threads),
20636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            // we must repeatedly poll these values each time.
20736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            while (mQueueSize.get() > mMaxSize.get()) {
20836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall                mQueue.discardNext();
20936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            }
21036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        } finally {
21136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall            mAvailableTicketCount.unfreeze();
21236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall        }
21336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall    }
21412f608f3d2089439a108788a1908941eea4277b9Puneet Lall}
215